golang实现iOS设备消息推送通知服务插件库Chanify的使用

Golang实现iOS设备消息推送通知服务插件库Chanify的使用

Chanify是一个安全简单的通知工具,开发者、系统管理员等任何人都可以通过API推送通知。

主要特性

  • 为通知自定义频道
  • 部署自己的节点服务器
  • 分布式架构设计
  • 隐私保护设计
  • 支持文本/图片/音频/文件消息格式

安装Chanify

预编译二进制文件

发布页面下载预编译的二进制文件。

Docker安装

$ docker pull wizjin/chanify:latest

从源码安装

$ git clone https://github.com/chanify/chanify.git
$ cd chanify
$ make install

使用示例

作为发送客户端

package main

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"
)

func main() {
	// 发送文本消息
	endpoint := "http://<address>:<port>"
	token := "<your_token>"
	message := "Hello from Golang!"

	// 使用form表单方式发送
	data := url.Values{}
	data.Set("text", message)
	
	resp, err := http.PostForm(
		fmt.Sprintf("%s/v1/sender/%s", endpoint, token),
		data,
	)
	if err != nil {
		fmt.Println("发送失败:", err)
		return
	}
	defer resp.Body.Close()
	
	fmt.Println("发送成功,状态码:", resp.StatusCode)
}

作为无状态节点(Serverless)

package main

import (
	"fmt"
	"net/http"
)

func main() {
	// 启动Chanify无状态节点
	http.HandleFunc("/v1/sender/", func(w http.ResponseWriter, r *http.Request) {
		// 处理发送请求
		token := strings.TrimPrefix(r.URL.Path, "/v1/sender/")
		fmt.Println("收到发送请求,token:", token)
		
		// 这里添加转发到api.chanify.net的逻辑
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("消息已接收"))
	})

	fmt.Println("启动服务器在 :8080")
	http.ListenAndServe(":8080", nil)
}

作为有状态节点(Serverful)

package main

import (
	"database/sql"
	"fmt"
	"net/http"
	"strings"
	
	_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func main() {
	// 初始化MySQL连接
	var err error
	db, err = sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/chanify?charset=utf8mb4&parseTime=true&loc=Local")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// 启动Chanify有状态节点
	http.HandleFunc("/v1/sender/", func(w http.ResponseWriter, r *http.Request) {
		token := strings.TrimPrefix(r.URL.Path, "/v1/sender/")
		
		// 查询设备token
		var deviceToken string
		err := db.QueryRow("SELECT device_token FROM devices WHERE user_token = ?", token).Scan(&deviceToken)
		if err != nil {
			http.Error(w, "无效token", http.StatusBadRequest)
			return
		}
		
		// 这里添加发送到APNS的逻辑
		fmt.Println("发送消息到设备:", deviceToken)
		
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("消息已发送"))
	})

	fmt.Println("启动服务器在 :8080")
	http.ListenAndServe(":8080", nil)
}

HTTP API示例

发送文本消息

func sendTextMessage() {
	client := &http.Client{}
	
	// 准备请求数据
	data := url.Values{}
	data.Set("text", "Hello from Golang!")
	data.Set("title", "通知标题")
	data.Set("sound", "1")
	data.Set("priority", "10")
	
	// 创建请求
	req, err := http.NewRequest(
		"POST", 
		"http://<address>:<port>/v1/sender/<token>",
		strings.NewReader(data.Encode()),
	)
	if err != nil {
		fmt.Println("创建请求失败:", err)
		return
	}
	
	// 设置Content-Type
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	
	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("发送请求失败:", err)
		return
	}
	defer resp.Body.Close()
	
	fmt.Println("响应状态码:", resp.StatusCode)
}

发送图片消息

func sendImageMessage() {
	// 打开图片文件
	file, err := os.Open("image.jpg")
	if err != nil {
		fmt.Println("打开图片失败:", err)
		return
	}
	defer file.Close()

	// 准备multipart表单
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	
	part, err := writer.CreateFormFile("image", "image.jpg")
	if err != nil {
		fmt.Println("创建表单文件失败:", err)
		return
	}
	
	_, err = io.Copy(part, file)
	if err != nil {
		fmt.Println("复制文件内容失败:", err)
		return
	}
	
	err = writer.Close()
	if err != nil {
		fmt.Println("关闭writer失败:", err)
		return
	}

	// 创建请求
	req, err := http.NewRequest(
		"POST",
		"http://<address>:<port>/v1/sender/<token>",
		body,
	)
	if err != nil {
		fmt.Println("创建请求失败:", err)
		return
	}
	
	// 设置Content-Type
	req.Header.Set("Content-Type", writer.FormDataContentType())
	
	// 发送请求
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("发送请求失败:", err)
		return
	}
	defer resp.Body.Close()
	
	fmt.Println("响应状态码:", resp.StatusCode)
}

配置示例

创建配置文件 ~/.chanify.yml:

server:
    host: 0.0.0.0   # 监听IP地址
    port: 8080      # 监听端口
    endpoint: http://my.server/path # 端点URL
    name: 我的节点   # 节点服务器名称
    secret: <secret code> # 无状态节点服务器的密钥
    datapath: ~/.chanify # 有状态节点服务器的数据存储路径
    dburl: mysql://root:password@tcp(127.0.0.1:3306)/chanify?charset=utf8mb4&parseTime=true&loc=Local
    http:
        - readtimeout: 10s  # HTTP读取超时
        - writetimeout: 10s # HTTP写入超时
    register:
        enable: false # 禁用用户注册
        whitelist:    # 白名单
            - <user id 1>
            - <user id 2>

client: # 发送客户端配置
    sound: 1    # 启用声音
    endpoint: http://default.node.server
    token: <default token>
    interruption-level: active

安全配置

禁用注册并设置白名单

package main

import (
	"fmt"
	"net/http"
	"strings"
)

var whitelist = map[string]bool{
	"user1_id": true,
	"user2_id": true,
}

func main() {
	http.HandleFunc("/v1/register", func(w http.ResponseWriter, r *http.Request) {
		// 检查用户ID是否在白名单中
		userID := r.URL.Query().Get("user_id")
		if !whitelist[userID] {
			http.Error(w, "注册已禁用", http.StatusForbidden)
			return
		}
		
		// 处理注册逻辑...
		fmt.Fprintf(w, "用户 %s 注册成功", userID)
	})

	fmt.Println("启动服务器在 :8080")
	http.ListenAndServe(":8080", nil)
}

完整示例

以下是一个完整的Golang示例,演示如何使用Chanify发送各种类型的通知:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
)

type ChanifyClient struct {
	Endpoint string
	Token    string
}

func NewChanifyClient(endpoint, token string) *ChanifyClient {
	return &ChanifyClient{
		Endpoint: endpoint,
		Token:    token,
	}
}

// SendText 发送文本消息
func (c *ChanifyClient) SendText(text, title string, sound bool, priority int) error {
	data := url.Values{}
	data.Set("text", text)
	
	if title != "" {
		data.Set("title", title)
	}
	
	if sound {
		data.Set("sound", "1")
	}
	
	if priority > 0 {
		data.Set("priority", fmt.Sprintf("%d", priority))
	}
	
	resp, err := http.PostForm(
		fmt.Sprintf("%s/v1/sender/%s", c.Endpoint, c.Token),
		data,
	)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("请求失败,状态码: %d", resp.StatusCode)
	}
	
	return nil
}

// SendImage 发送图片消息
func (c *ChanifyClient) SendImage(imagePath string) error {
	file, err := os.Open(imagePath)
	if err != nil {
		return err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	
	part, err := writer.CreateFormFile("image", "image.jpg")
	if err != nil {
		return err
	}
	
	_, err = io.Copy(part, file)
	if err != nil {
		return err
	}
	
	err = writer.Close()
	if err != nil {
		return err
	}

	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf("%s/v1/sender/%s", c.Endpoint, c.Token),
		body,
	)
	if err != nil {
		return err
	}
	
	req.Header.Set("Content-Type", writer.FormDataContentType())
	
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("请求失败,状态码: %d", resp.StatusCode)
	}
	
	return nil
}

// SendJSON 发送JSON格式的复杂消息
func (c *ChanifyClient) SendJSON(message map[string]interface{}) error {
	jsonData, err := json.Marshal(message)
	if err != nil {
		return err
	}
	
	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf("%s/v1/sender/%s", c.Endpoint, c.Token),
		bytes.NewBuffer(jsonData),
	)
	if err != nil {
		return err
	}
	
	req.Header.Set("Content-Type", "application/json")
	
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("请求失败,状态码: %d", resp.StatusCode)
	}
	
	return nil
}

func main() {
	// 初始化客户端
	client := NewChanifyClient("http://your.chanify.server:8080", "your_token_here")

	// 示例1: 发送简单文本消息
	err := client.SendText("这是一条测试消息", "测试标题", true, 10)
	if err != nil {
		fmt.Println("发送文本消息失败:", err)
	} else {
		fmt.Println("文本消息发送成功")
	}

	// 示例2: 发送图片消息
	err = client.SendImage("test.jpg")
	if err != nil {
		fmt.Println("发送图片消息失败:", err)
	} else {
		fmt.Println("图片消息发送成功")
	}

	// 示例3: 发送复杂JSON消息
	message := map[string]interface{}{
		"text":  "这是一条复杂消息",
		"title": "复杂通知",
		"sound": true,
		"priority": 10,
		"actions": []string{
			"查看详情|http://example.com/details",
			"忽略|chanify://action/ignore",
		},
		"timeline": map[string]interface{}{
			"code": "timeline123",
			"items": map[string]string{
				"key1": "value1",
				"key2": "value2",
			},
		},
	}
	
	err = client.SendJSON(message)
	if err != nil {
		fmt.Println("发送JSON消息失败:", err)
	} else {
		fmt.Println("JSON消息发送成功")
	}
}

这个示例展示了如何使用Golang与Chanify集成,包括发送文本、图片和复杂JSON格式的消息。你可以根据自己的需求扩展这个客户端,添加更多功能如发送音频、文件等。


更多关于golang实现iOS设备消息推送通知服务插件库Chanify的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现iOS设备消息推送通知服务插件库Chanify的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用Golang实现iOS设备消息推送通知服务(Chanify)

Chanify是一个开源的iOS消息推送服务,它允许开发者通过简单的API向iOS设备发送推送通知。下面我将介绍如何在Golang中使用Chanify实现iOS设备消息推送。

1. 准备工作

首先,你需要在iOS设备上安装Chanify应用,并获取你的设备token。

  1. 从App Store安装Chanify应用
  2. 打开应用,获取你的设备token(形如:CTxxxx...)

2. Golang实现Chanify推送

基本实现

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

// ChanifyNotification 定义Chanify通知结构
type ChanifyNotification struct {
	Title   string `json:"title"`
	Text    string `json:"text"`
	Sound   bool   `json:"sound,omitempty"`
	Actions string `json:"actions,omitempty"`
	Copy    string `json:"copy,omitempty"`
}

// SendChanifyNotification 发送Chanify通知
func SendChanifyNotification(token string, notification ChanifyNotification) error {
	url := fmt.Sprintf("https://api.chanify.net/v1/sender/%s", token)
	
	payload, err := json.Marshal(notification)
	if err != nil {
		return fmt.Errorf("failed to marshal notification: %v", err)
	}

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(payload))
	if err != nil {
		return fmt.Errorf("failed to send request: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	return nil
}

func main() {
	// 替换为你的Chanify设备token
	deviceToken := "CTxxxx...your_token_here"

	notification := ChanifyNotification{
		Title: "重要通知",
		Text:  "这是一条来自Golang的测试消息",
		Sound: true,
	}

	err := SendChanifyNotification(deviceToken, notification)
	if err != nil {
		fmt.Printf("发送通知失败: %v\n", err)
		return
	}

	fmt.Println("通知发送成功!")
}

高级功能实现

Chanify还支持更多高级功能,如自定义声音、动作按钮等:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

// AdvancedChanifyNotification 高级Chanify通知结构
type AdvancedChanifyNotification struct {
	Title     string `json:"title"`
	Text      string `json:"text"`
	Sound     int    `json:"sound,omitempty"` // 0-系统声音,1-铃音,2-警报
	Priority  int    `json:"priority,omitempty"` // 0-默认,1-被动,5-低,10-高
	Actions   string `json:"actions,omitempty"`  // 动作按钮,格式:"action1|http://url1,action2|http://url2"
	Copy      string `json:"copy,omitempty"`     // 点击后复制的内容
	AutoCopy  int    `json:"autocopy,omitempty"` // 0-不自动复制,1-自动复制
	Timestamp int64  `json:"timestamps,omitempty"` // 时间戳
}

// SendAdvancedChanifyNotification 发送高级Chanify通知
func SendAdvancedChanifyNotification(token string, notification AdvancedChanifyNotification) error {
	url := fmt.Sprintf("https://api.chanify.net/v1/sender/%s", token)
	
	// 如果没有设置时间戳,使用当前时间
	if notification.Timestamp == 0 {
		notification.Timestamp = time.Now().Unix()
	}

	payload, err := json.Marshal(notification)
	if err != nil {
		return fmt.Errorf("failed to marshal notification: %v", err)
	}

	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	if err != nil {
		return fmt.Errorf("failed to create request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send request: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	return nil
}

func main() {
	// 替换为你的Chanify设备token
	deviceToken := "CTxxxx...your_token_here"

	notification := AdvancedChanifyNotification{
		Title:    "高级通知",
		Text:     "这是一条包含高级功能的消息",
		Sound:    1, // 使用铃音
		Priority: 10, // 高优先级
		Actions:  "查看详情|https://example.com,忽略|",
		Copy:     "复制这段文本",
		AutoCopy: 1,
	}

	err := SendAdvancedChanifyNotification(deviceToken, notification)
	if err != nil {
		fmt.Printf("发送通知失败: %v\n", err)
		return
	}

	fmt.Println("高级通知发送成功!")
}

3. 使用自建Chanify服务器

如果你有自己的Chanify服务器,可以修改API端点:

func SendChanifyNotification(token string, notification ChanifyNotification, endpoint string) error {
    if endpoint == "" {
        endpoint = "https://api.chanify.net"
    }
    url := fmt.Sprintf("%s/v1/sender/%s", endpoint, token)
    // 其余代码相同...
}

4. 错误处理和重试机制

为了更健壮的实现,可以添加错误处理和重试:

func SendWithRetry(token string, notification ChanifyNotification, retries int) error {
    var lastErr error
    
    for i := 0; i < retries; i++ {
        err := SendChanifyNotification(token, notification)
        if err == nil {
            return nil
        }
        lastErr = err
        time.Sleep(time.Second * time.Duration(i+1)) // 指数退避
    }
    
    return fmt.Errorf("after %d retries, last error: %v", retries, lastErr)
}

5. 总结

通过上述代码,你可以轻松地在Golang中集成Chanify推送服务,实现向iOS设备发送通知的功能。Chanify的优势在于:

  1. 开源且免费
  2. 不需要Apple开发者账号
  3. 支持自建服务器
  4. 提供丰富的通知定制选项

你可以根据实际需求扩展这些基础功能,构建更复杂的通知系统。

回到顶部