本博客的留言板功能过于简单,只能留言,无法做到消息提醒
由于是纯静态网站,想再增强下留言板功能还是有办法的,可以使用云函数定时监听数据库在 N 分钟是否有新增数据,如果有新留言就推送到自己的微信上
Server 酱:利用微信服务号的模板消息做消息推送的服务,免费
云函数:腾讯云的无服务器云函数服务,支持各种语言,支持多种触发方式,免费
Bmob 云:云数据库,本博客在用的,免费
准备
注册 Server 酱,阅读下接口文档,推消息很简单就是一个 Post 请求
注册腾讯云,阅读下云函数的文档和部署流程
注册 Bmob 云,阅读 JavaScript 的 SDK 和 REST API 的使用
代码
这里使用 Go 实现
先安装下云函数的 SDK
go get -u github.com/tencentyun/scf-go-lib/cloudfunction
使用 Server 酱推送消息到微信,一个 Post 请求
type MessageResp struct {
ErrNo int `json:"errno"`
ErrMsg string `json:"errmsg"`
DateSet string `json:"dataset"`
}
// 推送消息到微信
func PushMessage(title, content, pushURL string) error {
resp, err := http.PostForm(pushURL, url.Values{"text": {title}, "desp": {content}})
if err != nil {
return err
}
defer resp.Body.Close()
bt, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
messageResp := MessageResp{}
if err = json.Unmarshal(bt, &messageResp); err != nil {
return err
}
if messageResp.ErrNo != 0 {
return errors.New(messageResp.ErrMsg)
}
return nil
}
获取多少分钟内的留言,使用 Bmob 的 REST API 实现。部署的过程中出现了 8 小时差的 BUG,统一时区就解决了
type Comment struct {
Nickname string `json:"nickname"`
Email string `json:"email"`
Website string `json:"website"`
Content string `json:"content"`
}
type RespBody struct {
Code int `json:"code"`
Error string `json:"error"`
Results []Comment `json:"results"`
}
// 获取xx分钟内的留言
func PullComment(period int, appID, restKey string) ([]Comment, error) {
req, _ := http.NewRequest("GET", restURL, nil)
// 请求头
req.Header.Add("Content-Type", "application/json")
req.Header.Add("X-Bmob-Application-Id", appID)
req.Header.Add("X-Bmob-REST-API-Key", restKey)
// 构造参数
q := req.URL.Query()
q.Add("order", "-createdAt")
var cstSh, _ = time.LoadLocation("Asia/Shanghai") // 统一时区,避免8小时差的BUG
sDate := time.Now().In(cstSh).Add(-1 * time.Duration(period) * time.Minute).Format("2006-01-02 15:04:05")
q.Add("where", `{"createdAt":{"$gte":{"__type": "Date", "iso": "`+sDate+`"}}}`)
// 设置参数
req.URL.RawQuery = q.Encode()
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
bt, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
respBody := RespBody{}
if err = json.Unmarshal(bt, &respBody); err != nil {
return nil, err
}
if respBody.Code != 0 {
return nil, errors.New(respBody.Error)
}
return respBody.Results, nil
}
最后的 main 函数
// 定时触发器在触发函数时,会把如下的数据结构封装在 event 里传给云函数
// 同时,定时触发器支持自定义传入 Message,缺省为空,注意Message会被转为字符串
// {
// "Type":"timer",
// "TriggerName":"EveryDay",
// "Time":"2019-02-21T11:49:00Z",
// "Message":"user define msg body"
// }
type Message struct {
MessageURL string `json:"MessageURL"`
Period int `json:"Period"`
AppID string `json:"AppID"`
RestKey string `json:"RestKey"`
}
type DefineEvent struct {
Type string `json:"Type"`
TriggerName string `json:"TriggerName"`
Time time.Time `json:"Time"`
Message string `json:"Message"`
}
func ListenAndSendMsg(ctx context.Context, event DefineEvent) (string, error) {
message := &Message{}
err := json.Unmarshal([]byte(event.Message), message)
if err != nil {
return "[]", err
}
comments, err := tool.PullComment(message.Period, message.AppID, message.RestKey)
if err != nil {
return "[]", err
}
if len(comments) == 0 {
return "[]", nil
}
jsonResult, err := json.Marshal(comments)
if err != nil {
return "[]", err
}
err = tool.PushMessage("新留言通知", string(jsonResult), message.MessageURL)
if err != nil {
return "[]", err
}
return string(jsonResult), nil
}
func main() {
cloudfunction.Start(ListenAndSendMsg)
}
打包部署
使用 Go 的交叉编译打包 linux 版本,然后打包上传到腾讯云函数上
测试通过
最后设置定时任务,每隔五分钟检测一次,然后把留言推送到我的微信
遇到的问题
关于云函数的外部传参文档上写的不太详细,在测试的时候你可以使用任何模板,而且在 Event 里面都能拿到
当放到线上运行的时候实际上是有固定模板的,关键是测试模板里有各种触发器的模板,唯独有没定时的模板,让人以为定时触发传参的就是那个 HelloWorld 的模板
意识到这一点后有又了代码,把参数以 json 的形式定义,最终在 event.Message 中拿到了,我有特意顶一个定时的模板用来测试,测试通过,上线后又出问题了,最终发现 Message 会被抓成字符串,自己定义的 DefineEvent 无法解析出来
无奈之下改了下模板,又改了下代码,从 Message 再二次解析,真是日了狗了
主要是测试麻烦,同时文档又不全,测试模板和生产的定时模板容易让人产生误会