或許你有用過或者聽說過《給未來寫封信》,這是由全知工坊開發的一款免費應用,你可以在此刻給自己或他人寫下一封信,然后選擇在未來的某一天寄出,想必那時收到信的人看著這封來自過往的信時一定會十分感動吧,
本文來自 Serverless 社區用戶「乂乂又又」供稿
這次我就帶大家一起來使用無服務器云函式 SCF 和物件存盤 COS,快速開發一個屬于自己的「給未來寫封信」應用,
效果展示
寫下一封信,然后投遞:

一封來自很久以前的信:

寫給未來的自己

你也可以訪問letter.idoo.top/letter來親自體驗一下(僅供測驗之用,不保證服務一直可用)
操作步驟
第一步:新建 python 云函式
參見我之前的系列文章《萬物皆可 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 = "[email protected]"
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" style="width: 80vw;">
<div style="height: 5vh;"></div>
<div id='letter_top'>
<p style="text-align: center;">開始寫信</p>
<wired-textarea id="letter" style="height: 320px;width: 300px;" placeholder="此刻平靜地寫下一封信,給未來的自己一份溫暖..." elevation="6" rows="14"></wired-textarea>
</div>
<div style="display: flex;align-items: center;justify-content: center;">
<div id='letter_left'>
<p style="text-align: center;">開始寫信</p>
<wired-textarea id="letter" style="height: 320px;width: 300px;" placeholder="此刻平靜地寫下一封信,給未來的自己一份溫暖..." elevation="6" rows="14"></wired-textarea>
</div>
<div style="width: 16px;"></div>
<div>
<p style="text-align: center;">送信日期</p>
<wired-calendar id="calendar"></wired-calendar>
</div>
</div>
<wired-divider style="margin: 16px 0;"></wired-divider>
<p id="hitokoto"></p>
<div>
<wired-input id="email" placeholder="收件郵箱"></wired-input>
<wired-button onclick="send()">投遞</wired-button>
</div>
<div style="height: 5vh;"></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://www.cnblogs.com/serverlesscloud/p/"https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
<script src="https://unpkg.com/[email protected]/lib/wired-elements-bundled.js "></script>
