苹果消息推送Apns是每个apple app必须面对的坎。目前捣腾的小项目需要定时向所有用户推送消息。之前使用python第三方库pyapns实现,结果运行一段时间发现内存消耗很高,而且推送不稳定,经常手机收不到消息。

于是尝试用GO语言单独写个循环推送的小程序。主要流程就是:定时(每隔2~4分钟),遍历所有的用户apple token,推送消息。

这里分享下代码:

查询出所有用户表中的apple token

用到的包:

import (
	"os"
	"strconv"
	"math/rand"
	"time"
  	"github.com/gohouse/gorose"
  	_ "github.com/go-sql-driver/mysql"
 	"log"
	"fmt"
	"github.com/sideshow/apns2"
	"github.com/sideshow/apns2/certificate"
	"github.com/sideshow/apns2/payload"

)

初始化数据库:

//初始化数据库
func init_db(){
	var dbConfig = map[string]interface{} {
        "Default":         "mysql_dev",// 默认数据库配置
        "SetMaxOpenConns": 0,          // (连接池)最大打开的连接数,默认值为0表示不限制
        "SetMaxIdleConns": 1,          // (连接池)闲置的连接数, 默认1
    
        "Connections":map[string]map[string]string{
            "mysql_dev": {// 定义名为 mysql_dev 的数据库配置
                "host": "127.0.0.1", // 数据库地址
                "username": "root",       // 数据库用户名
                "password": "123456",       // 数据库密码
                "port": "3306",            // 端口
                "database": "xxx",        // 链接的数据库名字
                "charset": "utf8",         // 字符集
                "protocol": "tcp",         // 链接协议
                "prefix": "",              // 表前缀
                "driver": "mysql",         // 数据库驱动(mysql,sqlite,postgres,oracle,mssql)
            },

    	},
    }
	connection, err := gorose.Open(dbConfig)
	if err != nil {
		fmt.Println(err)
		return
	}
	// close DB
	//defer connection.Close()
	
	Db = connection.GetInstance()

	}

从数库取出用户token:

users,err := Db.Table("users_userprofile").Fields("apple_token").Where("apple_token","!=","").Get()
fmt.Println("get all tokens end")
if err!=nil{
	fmt.Println(err)
	return
}
2.初始化APNs

用到的第三方包:

"github.com/sideshow/apns2"
"github.com/sideshow/apns2/certificate"
"github.com/sideshow/apns2/payload"

初始化:

//初始化APNSs
func initApns(){
	Info = log.New(os.Stdout,"Info:",log.Ldate | log.Ltime | log.Lshortfile)

	cert, err := certificate.FromP12File("./cert.p12", "123456")
	if err != nil {
		log.Fatal("Cert Error:", err)
	}
	Client = apns2.NewClient(cert).Production()
}
3.定义两个channel:
var (
	NotiChan chan *apns2.Notification
	responses chan *apns2.Response
)
4.遍历查询到的用户token,构建notification,传入NotiChan中
for _,item := range users{
	token:=item["apple_token"]
	fmt.Println("token",token)
	//生成job,放入Job channel, 供之后的worker去发送
	notification := &apns2.Notification{}
	notification.DeviceToken = token.(string)
	notification.Topic = "com.xxx.xxx"
	timeTamp:=time.Now().Unix()
	tm := time.Unix(timeTamp, 0)
	extras:=make(map[string]string)
	extras["command"]="wakeup"
	extras["msgId"]=strconv.FormatInt(timeTamp,10)
	extras["time"]=tm.Format("2006-01-02 03:04:05 PM")
	extras["counter"]=strconv.FormatInt(counter,10)

	payload := payload.NewPayload().Alert("").ContentAvailable()
	for k, v := range extras {
		payload.Custom(k, v)
	}

	notification.Payload = payload
	NotiChan <- notification
	//go sendApns(token.(string))
}
5.创建worker协程用于接受NotiChan中发来的notification,并发送到用户
//worker 从JobChan取出job, 用apns2.client发送notification
func worker_send(){
	for{
		n:= <- NotiChan
		fmt.Println("start sending notification")
		res, err := Client.Push(n)
		if err != nil {
			log.Fatal("Push Error:", err)
		}
		responses <- res
	}
}
6.创建协程,处理response
func work_response(){
	for{
		res := <- responses
		fmt.Println("%v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
	}
}
7.主线程逻辑,定时循环
//循环定时线程
func startLoop(){
	fmt.Println("start main loop")
	NotiChan=make(chan *apns2.Notification, 3000)
	responses = make(chan *apns2.Response, 3000)
	counter=0
	for{
		fmt.Println("start get all tokens")
		users,err := Db.Table("users_userprofile").Fields("apple_token").Where("apple_token","!=","").Get()
		fmt.Println("get all tokens end")
		if err!=nil{
			fmt.Println(err)
			return
		}

		for _,item := range users{
			token:=item["apple_token"]
			fmt.Println("token",token)
			//生成job,放入Job channel, 供之后的worker去发送
			notification := &apns2.Notification{}
			notification.DeviceToken = token.(string)
			notification.Topic = "com.xx.xxx"
			timeTamp:=time.Now().Unix()
			tm := time.Unix(timeTamp, 0)
			extras:=make(map[string]string)
			extras["command"]="wakeup"
			extras["msgId"]=strconv.FormatInt(timeTamp,10)
			extras["time"]=tm.Format("2006-01-02 03:04:05 PM")
			extras["counter"]=strconv.FormatInt(counter,10)

			payload := payload.NewPayload().Alert("").ContentAvailable()
			for k, v := range extras {
				payload.Custom(k, v)
			}

			notification.Payload = payload
			NotiChan <- notification
			//go sendApns(token.(string))
		}
		waitTime:=rand.Intn(120)
		fmt.Println("wait:",waitTime," seconds")
		time.Sleep(time.Duration(waitTime+120)*time.Second)
		counter=counter+1
	}
}
8. main函数中:
func main() {
 //起10个work,处理发送notification
 for i:=0;i<10;i++{
 go worker_send()
 }
 for i:=0;i<10;i++{
 go work_response()
 }
 startLoop()
}