Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"access_test.go",
"client_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//vendor/github.com/smartystreets/goconvey/convey:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"access.go",
"client.go",
"message.go",
],
importpath = "go-common/app/service/main/push/dao/huawei",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//library/stat:go_default_library",
"//library/stat/prom:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,65 @@
package huawei
// http://developer.huawei.com/consumer/cn/service/hms/catalog/huaweipush.html?page=hmssdk_huaweipush_api_reference_s1
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
const (
_accessTokenURL = "https://login.vmall.com/oauth2/token"
_grantType = "client_credentials"
_respCodeSuccess = 0
)
// Access huawei access token.
type Access struct {
AppID string
Token string
Expire int64
}
type accessResponse struct {
Token string `json:"access_token"`
Expire int64 `json:"expires_in"` // Access Token的有效期以秒为单位
Scope string `json:"scope"` // Access Token的访问范围即用户实际授予的权限列表
Code int `json:"error"`
Desc string `json:"error_description"`
}
// NewAccess get token.
func NewAccess(clientID, clientSecret string) (a *Access, err error) {
params := url.Values{}
params.Add("grant_type", _grantType)
params.Add("client_id", clientID)
params.Add("client_secret", clientSecret)
res, err := http.PostForm(_accessTokenURL, params)
if err != nil {
return
}
defer res.Body.Close()
dc := json.NewDecoder(res.Body)
resp := new(accessResponse)
if err = dc.Decode(resp); err != nil {
return
}
if resp.Code == _respCodeSuccess {
a = &Access{
AppID: clientID,
Token: resp.Token,
Expire: time.Now().Unix() + resp.Expire,
}
return
}
err = fmt.Errorf("new access error, code(%d) description(%s)", resp.Code, resp.Desc)
return
}
// IsExpired judge that whether privilige expired.
func (a *Access) IsExpired() bool {
return a.Expire <= time.Now().Add(8*time.Hour).Unix() // 提前8小时过期for renew auth
}

View File

@@ -0,0 +1,32 @@
package huawei
import (
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_NewAccess(t *testing.T) {
Convey("new access", t, func() {
ac, err := NewAccess("10125085", "iejq6hn3ds3d4neq1m21v443lmbm31gs")
if err != nil {
t.Errorf("new access error(%v)", err)
} else {
t.Log(ac.Token, ac.Expire)
}
})
}
func Test_AccessExpire(t *testing.T) {
Convey("access expire", t, func() {
ac := Access{Expire: time.Now().Add(-8 * time.Hour).Unix()}
if !ac.IsExpired() {
t.Errorf("access should be expire")
}
ac.Expire -= 10
if ac.IsExpired() {
t.Error("access should not be expire")
}
})
}

View File

@@ -0,0 +1,134 @@
package huawei
// http://developer.huawei.com/consumer/cn/service/hms/catalog/huaweipush.html?page=hmssdk_huaweipush_api_reference_s2
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"go-common/library/log"
"go-common/library/stat"
"go-common/library/stat/prom"
)
const (
_pushURL = "https://api.push.hicloud.com/pushsend.do"
_nspSvc = "openpush.message.api.send"
_ver = "1" // current SDK version
// ResponseCodeSuccess success code
ResponseCodeSuccess = "80000000"
// ResponseCodeSomeTokenInvalid some tokens failed
ResponseCodeSomeTokenInvalid = "80100000"
// ResponseCodeAllTokenInvalid all tokens failed
ResponseCodeAllTokenInvalid = "80100002"
// ResponseCodeAllTokenInvalidNew .
ResponseCodeAllTokenInvalidNew = "80300007"
)
var (
// ErrLimit .
ErrLimit = errors.New("触发华为系统级流控")
)
// Client huawei push http client.
type Client struct {
Access *Access
HTTPClient *http.Client
Stats stat.Stat
SDKCtx string
Package string
}
// ver huawei push service version.
type ver struct {
Ver string `json:"ver"`
AppID string `json:"appId"`
}
// NewClient new huawei push HTTP client.
func NewClient(pkg string, a *Access, timeout time.Duration) *Client {
ctx, _ := json.Marshal(ver{Ver: _ver, AppID: a.AppID})
return &Client{
Access: a,
HTTPClient: &http.Client{Timeout: timeout},
Stats: prom.HTTPClient,
SDKCtx: string(ctx),
Package: pkg,
}
}
/*
Push push notifications.
access_token: 必选使用OAuth2进行鉴权时的ACCESSTOKEN
nsp_ts: 必选服务请求时间戳自GMT 时间 1970-1-1 0:0:0至今的秒数。如果传入的时间与服务器时间相 差5分钟以上服务器可能会拒绝请求。
nsp_svc: 必选, 本接口固定为openpush.message.api.send
device_token_list: 以半角逗号分隔的华为PUSHTOKEN列表单次最多只是1000个
expire_time: 格式ISO 8601[6]:2013-06-03T17:30采用本地时间精确到分钟
payload: 描述投递消息的JSON结构体描述PUSH消息的:类型、内容、显示、点击动作、报表统计和扩展信 息。具体参考下面的详细说明。
*/
func (c *Client) Push(payload *Message, tokens []string, expire time.Time) (response *Response, err error) {
now := time.Now()
if c.Stats != nil {
defer func() {
c.Stats.Timing(_pushURL, int64(time.Since(now)/time.Millisecond))
log.Info("huawei stats timing: %v", int64(time.Since(now)/time.Millisecond))
if err != nil {
c.Stats.Incr(_pushURL, "failed")
}
}()
}
pl, _ := payload.SetPkg(c.Package).JSON()
reqURL := _pushURL + "?nsp_ctx=" + url.QueryEscape(c.SDKCtx)
tokenStr, _ := json.Marshal(tokens)
params := url.Values{}
params.Add("access_token", c.Access.Token)
params.Add("nsp_ts", strconv.FormatInt(now.Unix(), 10))
params.Add("nsp_svc", _nspSvc)
params.Add("device_token_list", string(tokenStr))
params.Add("expire_time", expire.Format("2006-01-02T15:04"))
params.Add("payload", pl)
req, err := http.NewRequest(http.MethodPost, reqURL, strings.NewReader(params.Encode()))
if err != nil {
log.Error("http.NewRequest() error(%v)", err)
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Connection", "Keep-Alive")
res, err := c.HTTPClient.Do(req)
if err != nil {
log.Error("HTTPClient.Do() error(%v)", err)
return
}
defer res.Body.Close()
if res.StatusCode == http.StatusServiceUnavailable {
return nil, ErrLimit
}
if res.StatusCode != http.StatusOK {
err = fmt.Errorf("huawei Push http code(%d)", res.StatusCode)
return
}
response = &Response{}
nspStatus := res.Header.Get("NSP_STATUS")
if nspStatus != "" {
log.Error("push huawei system error, NSP_STATUS(%s)", nspStatus)
response.Code = nspStatus
response.Msg = "NSP_STATUS error"
return
}
bs, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Error("ioutil.ReadAll() error(%v)", err)
return
}
if err = json.Unmarshal(bs, &response); err != nil {
log.Error("json decode body(%s) error(%v)", string(bs), err)
}
return
}

View File

@@ -0,0 +1,32 @@
package huawei
import (
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Push(t *testing.T) {
Convey("push huawei", t, func() {
// ac, err := NewAccess("10125085", "iejq6hn3ds3d4neq1m21v443lmbm31gs")
// if err != nil {
// t.Fatal(err)
// } else {
// t.Log(ac)
// }
// return
ac := &Access{
AppID: "10125085",
Token: "CFrF0b079efz2JUoDNBs1lwk9wtL4LfxExYqZvM3lAuDAeZcytQS3CPjYO6qMv9h+6FJoKrGIsQEwcKOmODdeg==",
Expire: 1522913725,
}
palyod := NewMessage().SetContent("huawei-content").SetTitle("huawei-title").SetCustomize("task_id", "123").SetCustomize("scheme", "bilibili://search/你好").SetIcon("http://pic.qiantucdn.com/58pic/12/38/18/13758PIC4GV.jpg")
c := NewClient("tv.danmaku.bili", ac, time.Minute)
// tokens := []string{"0866090037077934300001050400CN01"}
tokens := []string{"1", "2", ""}
res, err := c.Push(palyod, tokens, time.Now().Add(time.Hour))
So(err, ShouldBeNil)
t.Logf("huawei push res(%+v)", res)
})
}

View File

@@ -0,0 +1,179 @@
package huawei
import (
"encoding/json"
)
const (
// MsgTypePassthrough 消息类型:透传
MsgTypePassthrough = 1
// MsgTypeNotification 消息类型:通知栏消息
MsgTypeNotification = 3
// ActionTypeCustom 动作类型:自定义
ActionTypeCustom = 1
// ActionTypeURL 动作类型:打开URL
ActionTypeURL = 2
// ActionTypeAPP 动作类型:打开APP
ActionTypeAPP = 3
// CallbackTokenUninstalled 应用被卸载了
CallbackTokenUninstalled = 2
// CallbackTokenNotApply 终端安装了该应用但从未打开过未申请token所以不能展示
CallbackTokenNotApply = 5
// CallbackTokenInactive 非活跃设备,消息丢弃
CallbackTokenInactive = 10
)
// Response push response.
type Response struct {
Code string `json:"code"`
Msg string `json:"msg"`
Err string `json:"error"`
RequestID string `json:"requestId"`
}
// InvalidTokenResponse invalid tokens info in the push response.
type InvalidTokenResponse struct {
Success int `json:"success"`
Failure int `json:"failure"`
IllegalTokens []string `json:"illegal_tokens"`
}
// Message request message.
type Message struct {
Hps Hps `json:"hps"`
}
// Hps .
type Hps struct {
Msg Msg `json:"msg"`
Ext Ext `json:"ext"`
}
// Msg .
type Msg struct {
Type int `json:"type"`
Body Body `json:"body"`
Action Action `json:"action"`
}
// Body .
type Body struct {
Content string `json:"content"`
Title string `json:"title"`
}
// Action .
type Action struct {
Type int `json:"type"`
Param Param `json:"param"`
}
// Param .
type Param struct {
Intent string `json:"intent"`
AppPkgName string `json:"appPkgName"`
}
// Ext .
type Ext struct {
BiTag string `json:"biTag"`
Icon string `json:"icon"`
Customize []map[string]string `json:"customize"`
}
// Callback 华为推送回执(回调)
type Callback struct {
Statuses []*CallbackItem `json:"statuses"`
}
// CallbackItem http://developer.huawei.com/consumer/cn/service/hms/catalog/huaweipush_agent.html?page=hmssdk_huaweipush_devguide_server_agent#3.3 消息回执
type CallbackItem struct {
BiTag string `json:"biTag"`
AppID string `json:"appid"`
Token string `json:"token"`
Status int `json:"status"`
Timestamp int64 `json:"timestamp"`
}
// NewMessage get message.
func NewMessage() *Message {
return &Message{
Hps: Hps{
Msg: Msg{
Type: MsgTypeNotification, //1 透传异步消息, 3 系统通知栏异步消息 注意:2和4以后为保留后续扩展使用
Body: Body{
Content: "",
Title: "",
},
Action: Action{
Type: ActionTypeAPP, //1 自定义行为, 2 打开URL ,3 打开App
Param: Param{},
},
},
Ext: Ext{ //扩展信息含BI消息统计特定展示风格消息折叠。
BiTag: "Trump", // 设置消息标签如果带了这个标签会在回执中推送给CP用于检测某种类型消息的到达率和状态
},
},
}
}
// SetContent sets content.
func (m *Message) SetContent(content string) *Message {
m.Hps.Msg.Body.Content = content
return m
}
// SetTitle sets title.
func (m *Message) SetTitle(title string) *Message {
m.Hps.Msg.Body.Title = title
return m
}
// SetMsgType sets title.
func (m *Message) SetMsgType(typ int) *Message {
m.Hps.Msg.Type = typ
return m
}
// SetIntent sets intent.
func (m *Message) SetIntent(intent string) *Message {
m.Hps.Msg.Action.Param.Intent = intent
return m
}
// SetPkg sets app package name.
func (m *Message) SetPkg(pkg string) *Message {
m.Hps.Msg.Action.Param.AppPkgName = pkg
return m
}
// SetCustomize set ext info.
func (m *Message) SetCustomize(key, val string) *Message {
mp := map[string]string{key: val}
m.Hps.Ext.Customize = append(m.Hps.Ext.Customize, mp)
return m
}
// SetBiTag set biTag.
func (m *Message) SetBiTag(tag string) *Message {
m.Hps.Ext.BiTag = tag
return m
}
// SetIcon sets icon.
func (m *Message) SetIcon(url string) *Message {
m.Hps.Ext.Icon = url
return m
}
// JSON encode the message.
func (m *Message) JSON() (res string, err error) {
bytes, err := json.Marshal(m)
if err != nil {
return
}
res = string(bytes)
return
}