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 = ["client_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/push/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"constant.go",
"notification.go",
],
importpath = "go-common/app/service/main/push/dao/mi",
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,225 @@
package mi
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
"go-common/library/log"
"go-common/library/stat"
"go-common/library/stat/prom"
)
// Client Xiaomi http client.
type Client struct {
Header http.Header
HTTPClient *http.Client
Package string
URL string
Stats stat.Stat
}
// NewClient returns a Client with request header and auth.
func NewClient(pkg, auth string, timeout time.Duration) *Client {
header := http.Header{}
header.Set("Content-Type", "application/x-www-form-urlencoded")
header.Set("Authorization", AuthPrefix+auth)
// transport := &http.Transport{
// Proxy: func(_ *http.Request) (*url.URL, error) {
// return url.Parse("http://10.28.10.11:80")
// },
// DialContext: (&net.Dialer{
// Timeout: 30 * time.Second,
// KeepAlive: 30 * time.Second,
// DualStack: true,
// }).DialContext,
// MaxIdleConns: 100,
// IdleConnTimeout: 90 * time.Second,
// ExpectContinueTimeout: 1 * time.Second,
// }
return &Client{
Header: header,
HTTPClient: &http.Client{Timeout: timeout},
// HTTPClient: &http.Client{Timeout: timeout, Transport: transport},
Package: pkg,
Stats: prom.HTTPClient,
}
}
// SetProductionURL sets Production URL.
func (c *Client) SetProductionURL(url string) {
c.URL = ProductionHost + url
}
// SetDevelopmentURL sets Production URL.
func (c *Client) SetDevelopmentURL(url string) {
c.URL = DevHost + url
}
// SetVipURL sets VIP URL.
func (c *Client) SetVipURL(url string) {
c.URL = VipHost + url
}
// SetStatusURL sets feedback URL.
func (c *Client) SetStatusURL() {
c.URL = ProductionHost + StatusURL
}
// Push sends a Notification to Xiaomi push service.
func (c *Client) Push(xm *XMMessage) (response *Response, err error) {
if c.Stats != nil {
now := time.Now()
defer func() {
c.Stats.Timing(c.URL, int64(time.Since(now)/time.Millisecond))
log.Info("mi stats timing: %v", int64(time.Since(now)/time.Millisecond))
if err != nil {
c.Stats.Incr(c.URL, "failed")
}
}()
}
var req *http.Request
if req, err = http.NewRequest(http.MethodPost, c.URL, bytes.NewBuffer([]byte(xm.xmuv.Encode()))); err != nil {
log.Error("http.NewRequest() error(%v)", err)
return
}
req.Header = c.Header
var res *http.Response
if res, err = c.HTTPClient.Do(req); err != nil {
log.Error("HTTPClient.Do() error(%v)", err)
return
}
defer res.Body.Close()
response = &Response{}
var bs []byte
bs, err = ioutil.ReadAll(res.Body)
if err != nil {
log.Error("ioutil.ReadAll() error(%v)", err)
return
} else if len(bs) == 0 {
return
}
if e := json.Unmarshal(bs, &response); e != nil {
if e != io.EOF {
log.Error("json decode body(%s) error(%v)", string(bs), e)
}
}
return
}
// MockPush mock push.
func (c *Client) MockPush(xm *XMMessage) (response *Response, err error) {
if c.Stats != nil {
now := time.Now()
defer func() {
c.Stats.Timing(c.URL, int64(time.Since(now)/time.Millisecond))
if err != nil {
c.Stats.Incr(c.URL, "mi push mock")
}
}()
}
time.Sleep(200 * time.Millisecond)
response = &Response{Code: ResultCodeOk, Result: ResultOk}
return
}
// InvalidTokens get invalid tokens.
func (c *Client) InvalidTokens() (response *Response, err error) {
if c.Stats != nil {
now := time.Now()
defer func() {
c.Stats.Timing(c.URL, int64(time.Since(now)/time.Millisecond))
log.Info("mi invalidTokens timing: %v", int64(time.Since(now)/time.Millisecond))
if err != nil {
c.Stats.Incr(c.URL, "failed")
}
}()
}
req, err := http.NewRequest(http.MethodGet, feedbackHost+feedbackURI, nil)
if err != nil {
log.Error("http.NewRequest(%s) error(%v)", c.URL, err)
return
}
req.Header = c.Header
c.HTTPClient.Timeout = time.Minute
res, err := c.HTTPClient.Do(req)
if err != nil {
log.Error("HTTPClient.Do() error(%v)", err)
return
}
defer res.Body.Close()
response = &Response{}
bs, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Error("ioutil.ReadAll() error(%v)", err)
return
} else if len(bs) == 0 {
return
}
if e := json.Unmarshal(bs, &response); e != nil {
if e != io.EOF {
log.Error("json decode body(%s) error(%v)", string(bs), e)
}
}
return
}
// UninstalledTokens get uninstalled tokens.
func (c *Client) UninstalledTokens() (response *UninstalledResponse, err error) {
if c.Stats != nil {
now := time.Now()
defer func() {
c.Stats.Timing(c.URL, int64(time.Since(now)/time.Millisecond))
log.Info("mi UninstalledTokens timing: %v", int64(time.Since(now)/time.Millisecond))
if err != nil {
c.Stats.Incr(c.URL, "mi uninstalled tokens")
}
}()
}
req, err := http.NewRequest(http.MethodGet, emqHost+uninstalledURI+"?package_name="+c.Package, nil)
if err != nil {
log.Error("http.NewRequest(%s) error(%v)", c.URL, err)
return
}
req.Header = c.Header
c.HTTPClient.Timeout = time.Minute
res, err := c.HTTPClient.Do(req)
if err != nil {
log.Error("HTTPClient.Do() error(%v)", err)
return
}
defer res.Body.Close()
response = &UninstalledResponse{}
bs, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Error("ioutil.ReadAll() error(%v)", err)
return
} else if len(bs) == 0 {
return
}
if e := json.Unmarshal(bs, &response); e != nil {
if e != io.EOF {
log.Error("json decode body(%s) error(%v)", string(bs), e)
}
return
}
for _, s := range response.Result {
s = strings.Replace(s, `\`, "", -1)
s = strings.TrimPrefix(s, `"`)
s = strings.TrimSuffix(s, `"`)
ud := UninstalledData{}
if e := json.Unmarshal([]byte(s), &ud); e != nil {
log.Error("json unmarshal(%s) error(%v)", s, e)
continue
}
if ud.Token == "" {
continue
}
response.Data = append(response.Data, ud.Token)
}
return
}

View File

@@ -0,0 +1,75 @@
package mi
import (
"fmt"
"strconv"
"strings"
"testing"
"time"
"go-common/app/service/main/push/model"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Push(t *testing.T) {
Convey("push mi", t, func() {
xmm := &XMMessage{
Payload: "bili:///?type=bililive&roomid=33886",
RestrictedPackageName: "tv.danmaku.bili",
PassThrough: 0, // 0 表示通知栏消息1 表示透传消息
Title: model.DefaultMessageTitle,
Description: "直播推荐",
NotifyType: NotifyTypeDefaultAll,
TaskID: "vdsfdfs", // 每次不能相同,相同的只会推一次
}
// 设置是否被覆盖,不同的数字,可显示多行
xmm.SetNotifyID(xmm.TaskID)
xmm.SetCallbackParam("1")
xmm.SetRegID("device token")
// xmm.SetRegID("qlRyXrBPQ8ZkTg3x46hvTz3g8Oe/Fyz93XnE5U2NxRk=")
// xmm.SetUserAccount("15678567,25668444")
client := NewClient("tv.danmaku.bili", "QlcVxtNh6j7BXBPXjcbGoQ==", time.Hour)
// client.SetProductionURL(AccountURL)
client.SetVipURL(RegURL)
resp, err := client.Push(xmm)
So(err, ShouldBeNil)
So(resp.Code, ShouldEqual, ResultCodeNoValidTargets)
if resp.Result == ResultOk {
tt := strings.Split(resp.Info, " ")
if len(tt) == 6 {
m, _ := strconv.Atoi(tt[4])
fmt.Println(m + 1)
}
}
t.Logf("push xiaomi res(%+v)", resp)
// success: &{Result:ok Reason: Code:0 Data:{ID:scm01b20510561935064bK List:[]} Description:成功 Info:Received push messages for 1 REGID}
// failed: &{Result:error Reason:No valid targets! Code:20301 Data:{ID: List:[]} Description:发送消息失败 Info:}
})
}
// 需要测的时候再打开因为失效token获取完了就没了
// func Test_InvalidTokens(t *testing.T) {
// client := NewClient("tv.danmaku.bili", "QlcVxtNh6j7BXBPXjcbGoQ==", time.Hour)
// client.SetFeedbackURL()
// resp, err := client.InvalidTokens()
// if err != nil {
// t.Log(err)
// t.FailNow()
// }
// t.Log(resp)
// }
// 需要测的时候再打开因为卸载token获取完了就没了
// func Test_UninstalledTokens(t *testing.T) {
// client := NewClient("tv.danmaku.bili", "QlcVxtNh6j7BXBPXjcbGoQ==", time.Hour)
// resp, err := client.UninstalledTokens()
// if err != nil {
// t.Log(err)
// t.FailNow()
// }
// t.Log(resp)
// }

View File

@@ -0,0 +1,94 @@
package mi
// Xiaomi push service document: https://dev.mi.com/doc/cat=35/index.html
const (
// VipHost VIP host.
VipHost = "https://vip.api.xmpush.xiaomi.com"
// DevHost dev host.
DevHost = "https://sandbox.xmpush.xiaomi.com"
// ProductionHost production host.
ProductionHost = "https://api.xmpush.xiaomi.com"
// feedbackHost host to get invalid token.
feedbackHost = "https://feedback.xmpush.xiaomi.com"
// emqHost message queue
emqHost = "https://emq.xmpush.xiaomi.com"
// AuthPrefix auth prefix.
AuthPrefix = "key="
// ResultOk result status.
ResultOk = "ok" // "ok" means success, "error" means failed.
// ResultError result status.
ResultError = "error"
// ResultCodeOk result status code.
ResultCodeOk = 0
// ResultCodeNoValidTargets no valid token.
ResultCodeNoValidTargets = 20301
// ResultCodeNoMsgInEmq no message in emq.
ResultCodeNoMsgInEmq = 80002
// RegURL 向某个regid或一组regid列表推送某条消息
RegURL = "/v3/message/regid"
// AccountURL 根据account发送消息到指定account上
AccountURL = "/v2/message/user_account"
// MultiRegIDURL 针对不同的regid推送不同的消息
MultiRegIDURL = "/v2/multi_messages/regids"
// MultiAliasURL 针对不同的aliases推送不同的消息
MultiAliasURL = "/v2/multi_messages/aliases"
// MultiUserAccountURL 针对不同的accounts推送不同的消息
MultiUserAccountURL = "/v2/multi_messages/user_accounts"
// AliasURL 根据alias发送消息到指定设备上
AliasURL = "/v3/message/alias"
// MultiPackageNameMultiTopicURL 根据topic发送消息到指定一组设备上
MultiPackageNameMultiTopicURL = "/v3/message/multi_topic"
// MultiTopicURL 根据topic发送消息到指定一组设备上
MultiTopicURL = "/v2/message/topic"
// MultiPackageNameAllURL 向所有设备推送某条消息
MultiPackageNameAllURL = "/v3/message/all"
// AllURL 向所有设备推送某条消息
AllURL = "/v2/message/all"
// TopicURL 向多个topic广播消息
TopicURL = "/v3/message/multi_topic"
// ScheduleJobExistURL 检测定时消息的任务是否存在
ScheduleJobExistURL = "/v2/schedule_job/exist"
// ScheduleJobDeleteURL 删除指定的定时消息
ScheduleJobDeleteURL = "/v2/schedule_job/delete"
// ScheduleJobDeleteByJobKeyURL 删除指定的定时消息
ScheduleJobDeleteByJobKeyURL = "/v3/schedule_job/delete"
// feedbackURI 获取无效token列表
feedbackURI = "/v1/feedback/fetch_invalid_regids"
// uninstalledURI 获取卸载token列表
uninstalledURI = "/app/uninstall/regid"
// StatusURL 追踪消息
StatusURL = "/v1/trace/message/status"
// NotifyTypeDefaultAll 包括下面三种(notify type 可以是以下几种的OR组合)
NotifyTypeDefaultAll = -1
// NotifyTypeDefaultNone 声音、振动、led灯全关
NotifyTypeDefaultNone = 0
// NotifyTypeDefaultSound 使用默认提示音提示
NotifyTypeDefaultSound = 1
// NotifyTypeDefaultVibration 使用默认震动提示
NotifyTypeDefaultVibration = 2
// NotifyTypeDefaultLight 使用默认led灯光提示
NotifyTypeDefaultLight = 4
// NotPassThrough 显示通知
NotPassThrough = 0
// PassThrough 静默推送
PassThrough = 1
// CallbackURL 客户端收到后回调
CallbackURL = "https://api.bilibili.com/x/push/callback/xiaomi"
// CallbackBarStatusEnable .
CallbackBarStatusEnable = 1
// CallbackBarStatusDisable .
CallbackBarStatusDisable = 2
// CallbackBarStatusUnknown .
CallbackBarStatusUnknown = 3
// CallbackBarStatusEnableStr .
CallbackBarStatusEnableStr = "Enable"
// CallbackBarStatusDisableStr .
CallbackBarStatusDisableStr = "Disable"
// CallbackBarStatusUnknownStr .
CallbackBarStatusUnknownStr = "Unknown"
)

View File

@@ -0,0 +1,166 @@
package mi
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
)
// XMMessage define reference struct http://dev.xiaomi.com/doc/?p=533
type XMMessage struct {
Payload string // 消息的内容。
RestrictedPackageName string // App的包名。备注V2版本支持一个包名V3版本支持多包名中间用逗号分割
PassThrough int // pass_through的值可以为 0 表示通知栏消息1 表示透传消息
NotifyType int // 通知方式
Title string // 通知栏展示的通知的标题。
Description string // 通知栏展示的通知的描述。
TaskID string // 上报数据使用
xmuv url.Values // 含有本条消息所有属性的数组
}
func (xm *XMMessage) buildXMPostParam() {
xmuv := url.Values{}
xmuv.Set("payload", xm.Payload)
xmuv.Set("restricted_package_name", xm.RestrictedPackageName)
xmuv.Set("pass_through", strconv.Itoa(xm.PassThrough))
xmuv.Set("title", xm.Title)
xmuv.Set("description", xm.Description)
xmuv.Set("notify_type", strconv.Itoa(xm.NotifyType))
xmuv.Set("extra.task_id", xm.TaskID)
xmuv.Set("extra.jobkey", xm.TaskID)
xmuv.Set("extra.callback", CallbackURL)
xmuv.Set("extra.callback.type", "1") // 第三方所需要的回执类型。1:送达回执,2:点击回执,3:送达和点击回执,默认值为3。
xm.xmuv = xmuv
}
// SetNotifyID 可选项
// 默认情况下通知栏只显示一条推送消息。如果通知栏要显示多条推送消息需要针对不同的消息设置不同的notify_id相同notify_id的通知栏消息会覆盖之前的
// notify_id 0-4 同一个notifyId在通知栏只会保留一条
func (xm *XMMessage) SetNotifyID(notifyID string) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("notify_id", notifyID)
}
// SetNotifyType sound / vibration / led light
func (xm *XMMessage) SetNotifyType(typ int) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("notify_type", strconv.Itoa(typ))
}
// SetTimeToLive 可选项
// 如果用户离线设置消息在服务器保存的时间单位ms。服务器默认最长保留两周。
// time_to_live 可选项当用户离线是消息保留时间默认两周单位ms
func (xm *XMMessage) SetTimeToLive(expire int64) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
timeToLive := (expire - time.Now().Unix()) * 1000
xm.xmuv.Set("time_to_live", fmt.Sprintf("%d", timeToLive))
}
// SetTimeToSend 可选项
// 定时发送消息。用自1970年1月1日以来00:00:00.0 UTC时间表示以毫秒为单位的时间。注仅支持七天内的定时消息。
func (xm *XMMessage) SetTimeToSend(timeToSend int64) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("time_to_send", fmt.Sprintf("%d", timeToSend))
}
// SetUserAccount 根据user_account发送消息给设置了该user_account的所有设备。可以提供多个user_accountuser_account之间用“,”分割。参数仅适用于“/message/user_account”HTTP API。
func (xm *XMMessage) SetUserAccount(UserAccount string) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("user_account", UserAccount)
}
// SetUserAccounts 针对不同的userAccount推送不同的消息
// 根据user_accounts发送消息给设置了该user_account的所有设备。可以提供多个user_accountuser_account之间用“,”分割。
func (xm *XMMessage) SetUserAccounts(UserAccount string) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("user_accounts", UserAccount)
}
// SetRegID 根据registration_id发送消息到指定设备上。可以提供多个registration_id发送给一组设备不同的registration_id之间用“,”分割。
func (xm *XMMessage) SetRegID(deviceToken string) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("registration_id", deviceToken)
}
// SetTopic 根据topic发送消息给订阅了该topic的所有设备。参数仅适用于“/message/topic”HTTP API。
func (xm *XMMessage) SetTopic(UserAccount string) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("topic", UserAccount)
}
// SetCallbackParam 把应用标识传过去,这样方便区分应用
func (xm *XMMessage) SetCallbackParam(p string) {
if xm.xmuv == nil {
xm.buildXMPostParam()
}
xm.xmuv.Set("extra.callback.param", p) // 可选字段。第三方自定义回执参数最大长度64个字节这里用来存应用ID
}
// Response push result.
type Response struct {
Result string `json:"result,omitempty"` //“result”: string”ok” 表示成功, “error” 表示失败。
Reason string `json:"reason,omitempty"` //reason: string如果失败reason失败原因详情。
Code int `json:"code,omitempty"` //“code”: integer0表示成功非0表示失败。
Data Data `json:"data,omitempty"` //“data”: string本身就是一个json字符串其中id字段的值就是消息的Id
Description string `json:"description,omitempty"` //“description”: string 对发送消息失败原因的解释。
Info string `json:"info,omitempty"` //“info”: string详细信息。
TraceID string `json:"trace_id,omitempty"` // trace id for xiaomi
}
// Data response data.
type Data struct {
ID string `json:"id,omitempty"`
List []string `json:"list,omitempty"` // for feedback
Data json.RawMessage `json:"data,omitempty"` // for status
}
// UninstalledResponse .
type UninstalledResponse struct {
Code int `json:"errorCode,omitempty"`
Reason string `json:"reason,omitempty"`
Result []string `json:"result,omitempty"`
Data []string
}
// UninstalledData .
type UninstalledData struct {
Token string `json:"regId"`
Ts int64 `json:"ts"`
// Alias []string `json:"alias"` // 用不上
}
// RegidCallback regid callback
type RegidCallback struct {
AppID string `json:"app_id"`
AppVer string `json:"app_version"`
AppPkg string `json:"app_pkg"`
AppSecret string `json:"app_secret"`
Regid string `json:"regid"`
}
// Callback 推送回执(回调)
type Callback struct {
Param string `json:"param"` // 开发者上传的自定义参数值。
BarStatus string `json:"barStatus"` // 消息送达时通知栏的状态。Enable:为用户允许此app展示通知栏消息, Disable:为通知栏消息已关闭, Unknown:通知栏状态未知。
Type int `json:"type"` // callback类型
Targets string `json:"targets"` // 一批alias或者regId列表之间是用逗号分割
Jobkey string `json:"jobkey"`
}