Skip to content

Commit

Permalink
feat: 接入 one signal 的推送通知服务
Browse files Browse the repository at this point in the history
  • Loading branch information
axetroy committed May 26, 2020
1 parent f8e50a6 commit ba30112
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 98 deletions.
4 changes: 4 additions & 0 deletions .env
Expand Up @@ -38,6 +38,10 @@ REDIS_SERVER=localhost # Redis 服务器地址
REDIS_PORT=6379 # Redis 端口
REDIS_PASSWORD= # 连接服务器密码

# 推送服务器
ONE_SIGNAL_APP_ID="${ONE_SIGNAL_APP_ID}" # one signal 的 APP ID
ONE_SIGNAL_REST_API_KEY="${ONE_SIGNAL_REST_API_KEY}" # one signal 的 REST API KEY

# 短信服务设置
TELEPHONE_PROVIDER="aliyun" # 选用哪一家的短信服务,可选 `aliyun`

Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -38,6 +38,7 @@
| 新闻模块 | 新闻公告类的相关操作, CMS 内容 |
| 系统通知 | 系统通知的相关模块,主要用于`管理员发送给全员的通知` |
| 消息模块 | 用户的个人消息模块, 主要用于`管理员发送给某个用户的通知` |
| 推送模块 | APP 的推送通知模块,接入第三方 `onesignal` |
| 地址模块 | 用户设置相关的地址模块,例如`收货地址`|
| 上传模块 | 包含用户`上传文件/图片`的相关操作, 包含 `hash 去重`/`图片压缩`/`生成缩略图`|
| 下载模块 | 包含用户`下载文件/图片`的相关操作 |
Expand Down
22 changes: 16 additions & 6 deletions docs/config.md
Expand Up @@ -109,9 +109,19 @@

> 消费队列里面的消息
| 环境变量 | 类型 | 说明 | 默认值 |
| ---------------- | -------- | ---------------------------------------------- | ------------ |
| 通用配置 | - | - | - |
| GO_MOD | `string` | 处于开发模式(development)/生产模式(production) | `production` |
| MSG_QUEUE_SERVER | `string` | 消息队列服务器地址 | `localhost` |
| MSG_QUEUE_PORT | `int` | 消息队列服务器端口 | `4150` |
| 环境变量 | 类型 | 说明 | 默认值 |
| ----------------------- | -------- | ---------------------------------------------- | ------------ |
| 通用配置 | - | - | - |
| GO_MOD | `string` | 处于开发模式(development)/生产模式(production) | `production` |
| MSG_QUEUE_SERVER | `string` | 消息队列服务器地址 | `localhost` |
| MSG_QUEUE_PORT | `int` | 消息队列服务器端口 | `4150` |
| 数据库配置 | - | - | - |
| DB_HOST | `string` | 连接的数据库地址 | `localhost` |
| DB_PORT | `int` | 连接的数据库端口 | `65432` |
| DB_DRIVER | `string` | 数据库驱动器, 即数据库类型 | `postgres` |
| DB_NAME | `string` | 数据库名称 | `gotest` |
| DB_USERNAME | `string` | 连接数据库的用户名 | `gotest` |
| DB_PASSWORD | `string` | 连接数据库的密码 | `gotest` |
| 推送服务器 | - | - | - |
| ONE_SIGNAL_APP_ID | `string` | 推送服务器 one signal 的 APP ID | `` |
| ONE_SIGNAL_REST_API_KEY | `string` | 推送服务器 one signal 的 REST API KEY | `` |
13 changes: 7 additions & 6 deletions internal/app/message_queue_server/serve.go
Expand Up @@ -17,7 +17,7 @@ import (

func Serve() error {
var (
c *nsq.Consumer
consumers []*nsq.Consumer
)

redis.Connect()
Expand All @@ -27,7 +27,7 @@ func Serve() error {
if ctx, err := message_queue.RunMessageQueueConsumer(); err != nil {
log.Fatal(err)
} else {
c = ctx
consumers = ctx
}
}()

Expand All @@ -50,10 +50,11 @@ func Serve() error {

defer cancel()

if c != nil {
c.Stop()

_ = c.DisconnectFromNSQD(message_queue.Address)
if consumers != nil && len(consumers) > 0 {
for _, c := range consumers {
c.Stop()
_ = c.DisconnectFromNSQD(message_queue.Address)
}
}

// catching ctx.Done(). timeout of 5 seconds.
Expand Down
19 changes: 19 additions & 0 deletions internal/library/config/notify.go
@@ -0,0 +1,19 @@
// Copyright 2019-2020 Axetroy. All rights reserved. MIT license.
package config

import (
"github.com/axetroy/go-server/internal/service/dotenv"
)

type notify struct {
// https://documentation.onesignal.com/reference/create-notification
OneSignalAppID string `json:"one_signal_app_id"`
OneSignalRestApiKey string `json:"one_signal_rest_api_key"`
}

var Notify notify

func init() {
Notify.OneSignalAppID = dotenv.GetByDefault("ONE_SIGNAL_APP_ID", "")
Notify.OneSignalRestApiKey = dotenv.GetByDefault("ONE_SIGNAL_REST_API_KEY", "")
}
194 changes: 194 additions & 0 deletions internal/library/message_queue/main.go
@@ -0,0 +1,194 @@
// Copyright 2019-2020 Axetroy. All rights reserved. MIT license.
package message_queue

import (
"encoding/json"
"github.com/axetroy/go-server/internal/library/config"
"github.com/axetroy/go-server/internal/library/validator"
"github.com/axetroy/go-server/internal/schema"
"github.com/axetroy/go-server/internal/service/email"
"github.com/axetroy/go-server/internal/service/notify"
"github.com/axetroy/go-server/internal/service/redis"
"github.com/nsqio/go-nsq"
"log"
"net"
"sync"
"time"
)

type Topic string
type Chanel string

var (
TopicSendEmail Topic = "send_email"
ChanelSendEmail Chanel = "send_email"
TopicPushNotify Topic = "push_notify"
ChanelPushNotify Chanel = "push_notify"
Address string // 消息队列地址
Config *nsq.Config // 消息队列的配置
)

type SendActivationEmailBody struct {
Email string `json:"email" valid:"required~请输入邮箱"` // 要发送的邮箱
Code string `json:"code" valid:"required~请输入激活码"` // 发送的激活码
}

type SendNotifyBody struct {
Event notify.SendNotifyEvent `json:"event" valid:"required~请输入事件"` // 事件名称
Payload interface{} `json:"payload" valid:"required~请输入数据体"` // 数据体
}

func init() {
host := config.MessageQueue.Host
port := config.MessageQueue.Port

Address = net.JoinHostPort(host, port)

Config = nsq.NewConfig()
Config.DialTimeout = time.Second * 60
Config.MsgTimeout = time.Second * 60
Config.ReadTimeout = time.Second * 60
Config.WriteTimeout = time.Second * 60
Config.HeartbeatInterval = time.Second * 10
}

func RunMessageQueueConsumer() ([]*nsq.Consumer, error) {
consumers := make([]*nsq.Consumer, 0)
wg := &sync.WaitGroup{}

wg.Add(100)

emailConsumer, err := CreateConsumer(TopicSendEmail, ChanelSendEmail, nsq.HandlerFunc(func(message *nsq.Message) error {

body := SendActivationEmailBody{}

if err := json.Unmarshal(message.Body, &body); err != nil {
return err
}

mailer, err := email.NewMailer()

if err != nil {
return err
}

// 发送邮件
if err := mailer.SendActivationEmail(body.Email, body.Code); err != nil {
// 邮件没发出去的话,删除 redis 的 key
_ = redis.ClientActivationCode.Del(body.Code).Err()
}

log.Printf("发送验证码 %s 到 %s\n", body.Code, body.Email)

return nil
}))

if err != nil {
return consumers, err
} else {
consumers = append(consumers, emailConsumer)
}

notifyConsumer, err := CreateConsumer(TopicPushNotify, ChanelPushNotify, nsq.HandlerFunc(func(message *nsq.Message) error {

body := SendNotifyBody{}

if err := json.Unmarshal(message.Body, &body); err != nil {
return err
}

n := *notify.Notify

switch body.Event {
// 发送给所有用户
case notify.SendNotifyEventSendNotifyToAllUser:
type SendNotifyPayload struct {
Title string `json:"title" valid:"required"` // 推送的标题
Content string `json:"content" valid:"required"` // 推送的内容
}
b, err := json.Marshal(body.Payload)

if err != nil {
return err
}

var payload SendNotifyPayload

if err := json.Unmarshal(b, &payload); err != nil {
return err
}

if err := validator.ValidateStruct(payload); err != nil {
return err
}

if err := n.SendNotifyToAllUser(payload.Title, payload.Content); err != nil {
return err
}
break
// 发送给指定用户
case notify.SendNotifyEventSendNotifyToCustomUser:
type SendNotifyPayload struct {
UserID []string `json:"user_id" valid:"required"` // 要指定的推送用户 ID
Title string `json:"title" valid:"required"` // 推送的标题
Content string `json:"content" valid:"required"` // 推送的内容
}
b, err := json.Marshal(body.Payload)

if err != nil {
return err
}

var payload SendNotifyPayload

if err := json.Unmarshal(b, &payload); err != nil {
return err
}

if err := validator.ValidateStruct(payload); err != nil {
return err
}

if err := n.SendNotifyToCustomUser(payload.UserID, payload.Title, payload.Content); err != nil {
return err
}
break
// 发送给指定用户,登录异常
case notify.SendNotifyEventSendNotifyToLoginAbnormalUser:
b, err := json.Marshal(body.Payload)

if err != nil {
return err
}

var payload schema.ProfilePublic

if err := json.Unmarshal(b, &payload); err != nil {
return err
}

if err := validator.ValidateStruct(payload); err != nil {
return err
}

if err := n.SendNotifyToLoginAbnormalUser(payload); err != nil {
return err
}
break
default:
return nil
}

return nil
}))

if err != nil {
return consumers, err
} else {
consumers = append(consumers, notifyConsumer)
}

wg.Wait()

return consumers, nil
}
82 changes: 0 additions & 82 deletions internal/library/message_queue/message_queue.go

This file was deleted.

8 changes: 4 additions & 4 deletions internal/schema/profile.go
Expand Up @@ -30,10 +30,10 @@ type WechatBindingInfo struct {

// 公开的用户资料,任何人都可以查阅
type ProfilePublic struct {
Id string `json:"id"`
Username string `json:"username"`
Nickname *string `json:"nickname"`
Avatar string `json:"avatar"`
Id string `json:"id" valid:"required"`
Username string `json:"username" valid:"required"`
Nickname *string `json:"nickname" valid:"required"`
Avatar string `json:"avatar" valid:"required"`
}

type ProfileWithToken struct {
Expand Down

0 comments on commit ba30112

Please sign in to comment.