TMaize Blog

Server酱加云函数实现留言监控

本博客的留言板功能过于简单,只能留言,无法做到消息提醒

由于是纯静态网站,想再增强下留言板功能还是有办法的,可以使用云函数定时监听数据库在 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 版本,然后打包上传到腾讯云函数上

测试通过

01

02

最后设置定时任务,每隔五分钟检测一次,然后把留言推送到我的微信

03

遇到的问题

关于云函数的外部传参文档上写的不太详细,在测试的时候你可以使用任何模板,而且在 Event 里面都能拿到

当放到线上运行的时候实际上是有固定模板的,关键是测试模板里有各种触发器的模板,唯独有没定时的模板,让人以为定时触发传参的就是那个 HelloWorld 的模板

意识到这一点后有又了代码,把参数以 json 的形式定义,最终在 event.Message 中拿到了,我有特意顶一个定时的模板用来测试,测试通过,上线后又出问题了,最终发现 Message 会被抓成字符串,自己定义的 DefineEvent 无法解析出来

无奈之下改了下模板,又改了下代码,从 Message 再二次解析,真是日了狗了

主要是测试麻烦,同时文档又不全,测试模板和生产的定时模板容易让人产生误会