您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關如何基于Serverless使用 SCF+COS 給未來寫封信,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
或許你有用過或者聽說過《給未來寫封信》,這是由全知工坊開發的一款免費應用,你可以在此刻給自己或他人寫下一封信,然后選擇在未來的某一天寄出,想必那時收到信的人看著這封來自過往的信時一定會十分感動吧。
這次我就帶大家一起來使用無服務器云函數 SCF 和對象存儲 COS,快速開發一個屬于自己的「給未來寫封信」應用。
寫下一封信,然后投遞:
一封來自很久以前的信:
寫給未來的自己
你也可以訪問letter.idoo.top/letter來親自體驗一下(僅供測試之用,不保證服務一直可用)
參見我之前的系列文章《萬物皆可 Serverless 之使用 SCF+COS 快速開發全棧應用》
Life is short, show me the code.
老規矩,直接上代碼
import json import datetime import random from email.mime.text import MIMEText from email.header import Header import smtplib # 是否開啟本地debug模式 debug = False # 騰訊云對象存儲依賴 if debug: from qcloud_cos import CosConfig from qcloud_cos import CosS3Client from qcloud_cos import CosServiceError from qcloud_cos import CosClientError else: from qcloud_cos_v5 import CosConfig from qcloud_cos_v5 import CosS3Client from qcloud_cos_v5 import CosServiceError from qcloud_cos_v5 import CosClientError # 配置存儲桶 appid = '66666666666' secret_id = 'xxxxxxxxxxxxxxx' secret_key = 'xxxxxxxxxxxxxxx' region = 'ap-chongqing' bucket = 'name'+'-'+appid #配置發件郵箱 mail_host = "smtp.163.com" mail_user = "xxxxxxxxxx@163.com" mail_pass = "xxxxxxxxxxxxxx" mail_port = 465 # 對象存儲實例 config = CosConfig(Secret_id=secret_id, Secret_key=secret_key, Region=region) client = CosS3Client(config) #smtp郵箱實例 smtpObj = smtplib.SMTP_SSL(mail_host, mail_port) # cos 文件讀寫 def cosRead(key): try: response = client.get_object(Bucket=bucket, Key=key) txtBytes = response['Body'].get_raw_stream() return txtBytes.read().decode() except CosServiceError as e: return "" def cosWrite(key, txt): try: response = client.put_object( Bucket=bucket, Body=txt.encode(encoding="utf-8"), Key=key, ) return True except CosServiceError as e: return False #獲取所有信件 def getletters(): letterMap = {} letterTxt = cosRead('letters.txt') # 讀取數據 if len(letterTxt) > 0: letterMap = json.loads(letterTxt) return letterMap #添加信件 def addletter(date, email, letter): letterMap = getletters() if len(letterMap) > 0: letterMap[date+'_'+randomKey()] = email+'|'+letter return cosWrite('letters.txt', json.dumps(letterMap, ensure_ascii=False)) if len(letterMap) > 0 else False #刪除信件 def delletter(letter): letterMap = getletters() if len(letterMap) > 0: letterMap.pop(letter) return cosWrite('letters.txt', json.dumps(letterMap, ensure_ascii=False)) if len(letterMap) > 0 else False # 獲取今日日期 def today(): return datetime.datetime.now().strftime("%Y-%m-%d") # 判斷信件是否到期 def checkDate(t): return t[0:10] == today() # 根據時間生成uuid def randomKey(): return ''.join(random.sample('zyxwvutsrqponmlkjihgfedcba0123456789', 6)) # api網關回復消息格式化 def apiReply(reply, html=False, code=200): htmlStr = r'''<!DOCTYPE html> <html lang="zh-cn"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <title>給未來的自己寫封信</title> <style> html, body { padding: 0px; margin: 0px; height: 100vh; } .main { display: flex; flex-direction: column; justify-content: center; align-items: center; } .main_phone { display: flex; flex-direction: column; justify-content: start; align-items: center; } </style> </head> <body id='body'> <div class="main" > <div ></div> <div id='letter_top'> <p >開始寫信</p> <wired-textarea id="letter" placeholder="此刻平靜地寫下一封信,給未來的自己一份溫暖..." elevation="6" rows="14"></wired-textarea> </div> <div > <div id='letter_left'> <p >開始寫信</p> <wired-textarea id="letter" placeholder="此刻平靜地寫下一封信,給未來的自己一份溫暖..." elevation="6" rows="14"></wired-textarea> </div> <div ></div> <div> <p >送信日期</p> <wired-calendar id="calendar"></wired-calendar> </div> </div> <wired-divider ></wired-divider> <p id="hitokoto"></p> <div> <wired-input id="email" placeholder="收件郵箱"></wired-input> <wired-button onclick="send()">投遞</wired-button> </div> <div ></div> </div> <script> let datex = ''; let myEmail = document.getElementById('email'); let myLetter = document.getElementById('letter'); let myCalendar = document.getElementById('calendar'); let width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth let height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight let pc = width >= height let today = new Date(); let info = today.toString().split(' '); let selected = `${info[1]} ${today.getDate()}, ${today.getFullYear()}`; document.getElementById('body').classList.add(pc ? 'main' : 'main_phone'); document.getElementById('letter_left').style.display = pc ? 'block' : 'none'; document.getElementById('letter_top').style.display = pc ? 'none' : 'block'; myCalendar.setAttribute("selected", selected); myCalendar.addEventListener('selected', () => { let selectedObject = myCalendar.value; let date = new Date(new Date().setDate(selectedObject.date.getDate())); datex = date.toISOString().substr(0, 10); }); function send() { if (datex.length < 1 || myEmail.value.length < 1 || myLetter.value.length < 1) { alert('信件內容、送信日期或投遞郵箱不能為空'); return; } fetch(window.location.href, { method: 'POST', body: JSON.stringify({ date: datex, email: myEmail.value, letter: myLetter.value }) }).then(res => res.json()) .catch(error => console.error('Error:', error)) .then(response => alert(response.ok ? '添加成功:)' : '添加失敗:(')); } </script> <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script> <script src="https://unpkg.com/wired-elements@2.0.5/lib/wired-elements-bundled.js "></script> </body> </html>''' return { "isBase64Encoded": False, "statusCode": code, "headers": {'Content-Type': 'text/html' if html else 'application/json', "Access-Control-Allow-Origin": "*"}, "body": htmlStr if html else json.dumps(reply, ensure_ascii=False) } #登陸郵箱 def loginEmail(): try: smtpObj.login(mail_user, mail_pass) return True except smtplib.SMTPException as e: print(e) return False #發送郵件 def sendEmail(letter): temp=letter.split('|') receivers = [temp[0]] message = MIMEText(temp[1], 'plain', 'utf-8') message['From'] = mail_user message['To'] = temp[0] message['Subject'] = '一封來自很久以前的信' try: smtpObj.sendmail(mail_user, receivers, message.as_string()) print("send email success") return True except smtplib.SMTPException as e: print("Error: send email fail") return False #每天定時檢查需要發送的信件 def check_send_letters(): loginEmail() letters = getletters() for date in letters.keys(): if checkDate(date): sendEmail(letters[date]) def main_handler(event, context): if 'Time' in event.keys(): # 來自定時觸發器 check_send_letters() return if 'httpMethod' in event.keys(): # 來自api網關觸發器 if event['httpMethod'] == 'GET': return apiReply('', html=True) # 返回網頁 if event['httpMethod'] == 'POST': # 添加信件 body = json.loads(event['body']) flag = addletter(body['date'], body['email'], body['letter']) return apiReply({ 'ok': True if flag else False, 'message': '添加成功' if flag else '添加失敗' }) return apiReply('', html=True)
沒錯,這就是前面展示的網頁應用的全部源碼了,使用云函數 SCF 構建一個完整的前后端的全棧應用就是這么簡單。
代碼可能有點長,其實也沒多少知識點,下面咱們再一起捋一下這個云函數 ~
import json import datetime import random from email.mime.text import MIMEText from email.header import Header import smtplib # 是否開啟本地debug模式 debug = False # 騰訊云對象存儲依賴 if debug: from qcloud_cos import CosConfig from qcloud_cos import CosS3Client from qcloud_cos import CosServiceError from qcloud_cos import CosClientError else: from qcloud_cos_v5 import CosConfig from qcloud_cos_v5 import CosS3Client from qcloud_cos_v5 import CosServiceError from qcloud_cos_v5 import CosClientError
首先是依賴的導入,這里主要導入了 python 自帶的 email 模塊和騰訊云對象存儲 SDK,來實現信件的發送和后端存儲需求。
這里需要注意一點,在騰訊云的云函數在線運行環境中,已經安裝了 qcloud\_cos\_v5
對象存儲 SDK,而我在本地環境安裝的對象存儲 SDK 是 qcloud\_cos
,為了方便本地調試,這里我設置了一個 debug 開關,來動態導入 qcloud\_cos
依賴,這一點我在之前的系列文章《萬物皆可Serverless之使用SCF+COS快速開發全棧應用》中有講到。
# 配置存儲桶 appid = '66666666666' secret_id = 'xxxxxxxxxxxxxxx' secret_key = 'xxxxxxxxxxxxxxx' region = 'ap-chongqing' bucket = 'name'+'-'+appid #配置發件郵箱 mail_host = "smtp.163.com" mail_user = "xxxxxxxxxx@163.com" mail_pass = "xxxxxxxxxxxxxx" mail_port = 465
然后配置一下自己的郵箱信息和騰訊云對象存儲桶信息
配置完成之后,我們再來看一下云函數的入口函數 main_handler(event, context)
def main_handler(event, context): if 'Time' in event.keys(): # 來自定時觸發器 check_send_letters() return if 'httpMethod' in event.keys(): # 來自api網關觸發器 if event['httpMethod'] == 'GET': return apiReply('', html=True) # 返回網頁 if event['httpMethod'] == 'POST': # 添加信件 body = json.loads(event['body']) flag = addletter(body['date'], body['email'], body['letter']) return apiReply({ 'ok': True if flag else False, 'message': '添加成功' if flag else '添加失敗' }) return apiReply('', html=True)
這里我們根據event的keys里有無'Time'來判斷云函數是否是通過定時器來觸發的,
這一點我在之前的系列文章《萬物皆可 Serverless 之使用 SCF+COS 快速開發全棧應用》中有講到。
#每天定時檢查需要發送的信件 def check_send_letters(): loginEmail() letters = getletters() for date in letters.keys(): if checkDate(date): sendEmail(letters[date])
檢查云函數是否是通過定時器觸發,是因為在后面我們會給這個云函數添加定時觸發器來每天定時檢查需要發送的信件。
這里的 check\_send\_letters
函數的作用就是登錄我們的郵箱并讀取在 cos 中的所有信件,然后逐封檢查信件的發信日期,如果信件發信日期與當前的日期相符,就會向指定的郵箱發送信件,完成在指定日期投放信件的功能。
if event['httpMethod'] == 'GET': return apiReply('', html=True) # 返回網頁 if event['httpMethod'] == 'POST': # 添加信件 body = json.loads(event['body']) flag = addletter(body['date'], body['email'], body['letter']) return apiReply({ 'ok': True if flag else False, 'message': '添加成功' if flag else '添加失敗' })
如果我們的云函數是通過 api 網關觸發的話,就判斷一下 http 請求的方法是 GET 還是 POST
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <title>給未來的自己寫封信</title> <style> html, body { padding: 0px; margin: 0px; height: 100vh; } .main { display: flex; flex-direction: column; justify-content: center; align-items: center; } .main_phone { display: flex; flex-direction: column; justify-content: start; align-items: center; } </style> </head> <body id='body'> <div class="main" > <div ></div> <div id='letter_top'> <p >開始寫信</p> <wired-textarea id="letter" placeholder="此刻平靜地寫下一封信,給未來的自己一份溫暖..." elevation="6" rows="14"></wired-textarea> </div> <div > <div id='letter_left'> <p >開始寫信</p> <wired-textarea id="letter" placeholder="此刻平靜地寫下一封信,給未來的自己一份溫暖..." elevation="6" rows="14"></wired-textarea> </div> <div ></div> <div> <p >送信日期</p> <wired-calendar id="calendar"></wired-calendar> </div> </div> <wired-divider ></wired-divider> <p id="hitokoto"></p> <div> <wired-input id="email" placeholder="收件郵箱"></wired-input> <wired-button onclick="send()">投遞</wired-button> </div> <div ></div> </div> <script> let datex = ''; let myEmail = document.getElementById('email'); let myLetter = document.getElementById('letter'); let myCalendar = document.getElementById('calendar'); let width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth let height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight let pc = width >= height let today = new Date(); let info = today.toString().split(' '); let selected = `${info[1]} ${today.getDate()}, ${today.getFullYear()}`; document.getElementById('body').classList.add(pc ? 'main' : 'main_phone'); document.getElementById('letter_left').style.display = pc ? 'block' : 'none'; document.getElementById('letter_top').style.display = pc ? 'none' : 'block'; myCalendar.setAttribute("selected", selected); myCalendar.addEventListener('selected', () => { let selectedObject = myCalendar.value; let date = new Date(new Date().setDate(selectedObject.date.getDate())); datex = date.toISOString().substr(0, 10); }); function send() { if (datex.length < 1 || myEmail.value.length < 1 || myLetter.value.length < 1) { alert('信件內容、送信日期或投遞郵箱不能為空'); return; } fetch(window.location.href, { method: 'POST', body: JSON.stringify({ date: datex, email: myEmail.value, letter: myLetter.value }) }).then(res => res.json()) .catch(error => console.error('Error:', error)) .then(response => alert(response.ok ? '添加成功:)' : '添加失敗:(')); } </script> <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script> <script src="https://unpkg.com/wired-elements@2.0.5/lib/wired-elements-bundled.js "></script> </body> </html>
如果是 GET 請求就返回上面的前端網頁,也就是文章開頭第一張圖,再來瞅一眼
再來看下前端網頁的發信過程
function send() { if (datex.length < 1 || myEmail.value.length < 1 || myLetter.value.length < 1) { alert('信件內容、送信日期或投遞郵箱不能為空'); return; } fetch(window.location.href, { method: 'POST', body: JSON.stringify({ date: datex, email: myEmail.value, letter: myLetter.value }) }).then(res => res.json()) .catch(error => console.error('Error:', error)) .then(response => alert(response.ok ? '添加成功:)' : '添加失敗:(')); }
這里我們是向當前網頁地址,也是云函數的 api 網關地址 POST 了一個包含所有信件信息的 json 字符串
if event['httpMethod'] == 'POST': # 添加信件 body = json.loads(event['body']) flag = addletter(body['date'], body['email'], body['letter']) return apiReply({ 'ok': True if flag else False, 'message': '添加成功' if flag else '添加失敗' })
回到云函數后端,我們在收到 POST 請求之后,在 event 里拿到 POST 的請求體,并重新將 json 字符串轉成 map 對象,之后將 body 傳給 addletter 函數,將信件信息保存到 cos 里,然后向網頁前端回復信件是否添加成功。
這樣整個應用的前后端只用一個云函數就都實現了,是不是很酸爽呀( ?? ω ?? )y~
找到本地云函數文件夾下面的 template.yaml
配置文件
Resources: default: Type: 'TencentCloud::Serverless::Namespace' letter: Properties: CodeUri: ./ Type: Event Environment: Variables: Description: 給未來寫封信云函數 Handler: index.main_handler MemorySize: 64 Timeout: 3 Runtime: Python3.6 Events: timer: Type: Timer Properties: CronExpression: '0 0 8 * * * *' Enable: True letter_apigw: Type: APIGW Properties: StageName: release ServiceId: HttpMethod: ANY Type: 'TencentCloud::Serverless::Function'
這里主要配置了一下云函數的名稱,timer 觸發器和 api 網關觸發器,可以自行設置。
上述就是小編為大家分享的如何基于Serverless使用 SCF+COS 給未來寫封信了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。