作者 yangfu

增加推送服务 个推

package v1
import (
"encoding/json"
"gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
"openapi/controllers"
"openapi/protocol"
"openapi/services/push"
)
type PushController struct {
controllers.BaseController
}
//推送信息 PushInfo
// @router /pushInfo [post]
func (this *PushController) PushInfo() {
var msg *protocol.ResponseMessage
defer func() {
this.Resp(msg)
}()
var request *protocol.PushInfoRequest
if err := json.Unmarshal(this.ByteBody, &request); err != nil {
log.Error(err)
msg = protocol.BadRequestParam(1)
return
}
if b, m := this.Valid(request); !b {
msg = m
return
}
header := controllers.GetRequestHeader(this.Ctx)
msg = protocol.NewReturnResponse(push.PushInfo(header, request))
}
... ...
package push
/*
Map key
*/
const ()
... ...
package getui
import (
"crypto/sha256"
"fmt"
"github.com/astaxie/beego/httplib"
"gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
"openapi/internal/push"
"openapi/internal/utils"
"strings"
"sync"
"time"
)
var (
host = "https://restapi.getui.com"
pushSingle = "push_single"
authSign = "auth_sign "
)
var (
authtoken = ""
expire time.Time
authMux sync.RWMutex
expireSpan = time.Second * 3600 * 6
)
type GetuiNotification struct {
Options *push.Options
Request *httplib.BeegoHTTPRequest
}
func (notify *GetuiNotification) Init(options ...push.Option) error {
notify.Options = &push.Options{}
for _, o := range options {
o(notify.Options)
}
return nil
}
func (notify *GetuiNotification) Send(option map[string]interface{}) error {
token, err := notify.GetAuthToken()
if err != nil {
return err
}
var (
result *Result
url = notify.Url(notify.Options.AppId, pushSingle)
)
notify.Request = httplib.Post(url)
notify.Request.Header("authtoken", token)
notify.Request.JSONBody(notify.Message())
if err = notify.Request.ToJSON(&result); err != nil {
return err
}
notify.print(url, notify.Message(), result, result)
if err = handleResult(url, result); err != nil {
return err
}
return nil
}
func (notify *GetuiNotification) Message() interface{} {
//TODO:默认通知模板
m := NewNotificationTemplate(notify.Options)
return m
}
func (notify *GetuiNotification) Url(param string, method string) string {
return fmt.Sprintf("%v/v1/%v/%v", host, param, method)
}
func (notify *GetuiNotification) GetAuthToken() (token string, err error) {
if authtoken != "" && expire.Unix() > time.Now().Unix() {
token = authtoken
return
}
authMux.Lock()
defer authMux.Unlock()
url := notify.Url(notify.Options.AppId, authSign)
notify.Request = httplib.Post(strings.TrimSpace(url))
req := &AuthSignRequest{
Timestamp: fmt.Sprintf("%v", time.Now().Unix()*1000), //"1589797286000",//
AppKey: notify.Options.AppKey,
}
req.Sign = sign(req.AppKey, req.Timestamp, notify.Options.AppMasterSecret)
_, err = notify.Request.JSONBody(req)
if err != nil {
return
}
var rsp *AuthSignResponse
err = notify.Request.ToJSON(&rsp)
notify.print(url, req, rsp, rsp.Result)
if err != nil {
return
}
if err = handleResult(url, rsp.Result); err != nil {
return
}
authtoken = rsp.AuthToken
token = rsp.AuthToken
expire = time.Now().Add(expireSpan)
return
}
func handleResult(url string, result *Result) (err error) {
if strings.ToLower(result.Result) == "ok" {
return
}
err = fmt.Errorf("grequest fail,url:%v error:%v", url, result.Result)
return err
}
func sign(appkey, timestamp, mastersecret string) string {
sha := sha256.New()
sha.Write([]byte(appkey + timestamp + mastersecret))
return fmt.Sprintf("%x", sha.Sum(nil))
}
func (notify *GetuiNotification) print(url string, v interface{}, rsp interface{}, result *Result) {
if !notify.Options.DebugModule {
return
}
log.Error(fmt.Sprintf("【个推】 url:%v \n request:%v \n response:%v 结果:%v", url, utils.JsonAssertString(v), utils.JsonAssertString(rsp), result.Result))
}
... ...
package getui
import (
"openapi/internal/push"
"testing"
)
func TestGetui(t *testing.T) {
var param = make(map[string]interface{})
param["A"] = "A1"
param["B"] = 2
param["C"] = struct{ Id int }{Id: 10}
notification := &GetuiNotification{}
err := notification.Init(
push.DebugModule(true),
push.AppId("TkpBI4awmg9fBUx3NWKXS6"),
push.AppKey("5AjJeDOSOZ5ojQpXJFjhg9"),
push.AppMasterSecret("9VnM8MaA6n84Y5VnOIaSvA"),
push.ClientId("b5fff5f6b0af551da5f381fa47991828"),
push.MsgType(1),
push.Title("测试 hello"),
push.Content("hello content"),
push.TransmissionContent("扩展1111"),
push.Extra(param),
)
if err != nil {
t.Fatal(err)
}
err = notification.Send(param)
if err != nil {
t.Fatal(err)
}
}
... ...
package getui
import (
"gitlab.fjmaimaimai.com/mmm-go/gocomm/identity/uid"
"openapi/internal/push"
)
type Template struct {
ClientId string `json:"cid"`
RequestId string `json:"requestid"`
Message *Message `json:"message"`
}
//1.消息模板
type NotificationTemplate struct {
*Template
Notification *Notification `json:"notification"`
}
//2.透传模板
type TransmissionTemplate struct{}
type Notification struct {
Style *Style `json:"style"`
*Transmission
}
type Message struct {
AppKey string `json:"appkey"`
IsOffline bool `json:"is_offline"`
MsgType string `json:"msgtype"`
}
type Transmission struct {
TransmissionType bool `json:"transmission_type"`
TransmissionContent string `json:"transmission_content,omitempty"`
DurationBegin string `json:"duration_begin,omitempty"`
DurationEnd string `json:"duration_end,omitempty"`
}
func (o *Transmission) SetTransmissionType(t bool) {
o.TransmissionType = t
}
func (o *Transmission) SetTransmissionContent(s string) {
o.TransmissionContent = s
}
func (o *Transmission) SetDuration(begin, end string) {
o.DurationBegin = begin
o.DurationEnd = end
}
//1.新建单推
func NewNotificationTemplate(options *push.Options) *NotificationTemplate {
return &NotificationTemplate{
Template: NewTemplate(options),
Notification: &Notification{
Style: (&Style{}).SetStyle0(options),
Transmission: NewTransmission(options),
},
}
}
func NewTemplate(options *push.Options) *Template {
return &Template{
Message: NewMessage(options),
ClientId: options.ClientId,
RequestId: genRequestId(),
}
}
func NewTransmission(options *push.Options) *Transmission {
t := &Transmission{}
if len(options.TransmissionContent) == 0 {
t.SetTransmissionType(false)
return t
}
t.SetTransmissionType(true)
t.SetTransmissionContent(options.TransmissionContent)
return t
}
func NewMessage(options *push.Options) *Message {
return &Message{
AppKey: options.AppKey,
IsOffline: true,
MsgType: resolveMsgType(options.MsgType),
}
}
func resolveMsgType(msgType int) string {
/*
消息应用类型,
可选项:notification、link、notypopload、startactivity, transmission
*/
switch msgType {
case 1:
return "notification"
}
return "notification"
}
func genRequestId() string {
return uid.NewV1().StringNoDash()
}
//样式 0:系统样式 1:个推样式 4:纯图样式 6:展开通知样式
type Style struct {
Type int `json:"type"` //样式类型
Text string `json:"text"` //通知内容
Title string `json:"title"` //通知标题
Logo string `json:"logo,omitempty"` //通知的图标名称,包含后缀名(需要在客户端开发时嵌入),如“push.png”
//IsRing bool `json:"is_ring"` //收到通知是否响铃:true响铃,false不响铃。默认响铃
//IsVibrate bool `json:"is_vibrate"` //收到通知是否振动:true振动,false不振动。默认振动
}
func (s *Style) SetStyle0(options *push.Options) *Style {
s.Type = 0
s.Title = options.Title
s.Text = options.Content
s.Logo = "push.png" //TODO:设置Logo地址
return s
}
//认证请求/应答
type AuthSignRequest struct {
Sign string `json:"sign"`
Timestamp string `json:"timestamp"`
AppKey string `json:"appkey"`
}
type AuthSignResponse struct {
*Result
AuthToken string `json:"auth_token"`
}
type Result struct {
Result string `json:"result"`
TaskId string `json:"taskid"`
Status string `json:"status"`
Desc string `json:"desc"`
}
... ...
package push
type Options struct {
AppId string
AppKey string
AppSecret string
AppMasterSecret string
ClientId string
MsgType int
Title string
Content string
Extra interface{} //扩展数据
TransmissionContent string //透传内容
DebugModule bool
}
type Option func(o *Options)
//个推appId
func AppId(id string) Option {
return func(o *Options) {
o.AppId = id
}
}
//个推appKey
func AppKey(key string) Option {
return func(o *Options) {
o.AppKey = key
}
}
//个推appSecret
func AppSecret(secret string) Option {
return func(o *Options) {
o.AppSecret = secret
}
}
//个推appMasterSecret
func AppMasterSecret(secret string) Option {
return func(o *Options) {
o.AppMasterSecret = secret
}
}
//个推 app clientId
func ClientId(clientId string) Option {
return func(o *Options) {
o.ClientId = clientId
}
}
//消息类型
func MsgType(msgType int) Option {
return func(o *Options) {
o.MsgType = msgType
}
}
//消息内容
func Title(title string) Option {
return func(o *Options) {
o.Title = title
}
}
func Content(content string) Option {
return func(o *Options) {
o.Content = content
}
}
func Extra(extra interface{}) Option {
return func(o *Options) {
o.Extra = extra
}
}
func TransmissionContent(content string) Option {
return func(o *Options) {
o.TransmissionContent = content
}
}
func DebugModule(module bool) Option {
return func(o *Options) {
o.DebugModule = module
}
}
const (
Message = iota + 1
Notification
)
... ...
package push
type INotification interface {
Init(option ...Option) error
Send(option map[string]interface{}) error
}
... ...
... ... @@ -28,7 +28,7 @@ func (a UMAppAuth) GetAppAuth(appName string) *AppAuth {
if auth, ok := a[appName]; ok {
return auth
}
return nil
return a.GetAppAuth(ProjectDefault)
}
type AppAuth struct {
... ...
package umeng
import (
"crypto/md5"
"encoding/json"
"fmt"
"github.com/astaxie/beego/httplib"
"io/ioutil"
"net/http"
"openapi/internal/push"
"strings"
"time"
)
var (
Host = "http://msg.umeng.com"
ApiSend = "/api/send"
UploadPath = "/upload"
)
var FiledErrorFormat = "缺少字段:%v"
var FiledErrorFormatFunc = func(filed string) error {
return fmt.Errorf(FiledErrorFormat, filed)
}
const (
AppKey = "appkey"
Timestamp = "timestamp"
Type = "type"
ProductionMode = "production_mode"
DeviceTokens = "device_tokens"
Payload = "payload"
Policy = "policy"
Description = "description"
DisplayType = "displayType"
Body = "body"
Ticker = "ticker" // 必填,通知栏提示文字
Title = "title" // 必填,通知标题
Text = "text" //必填,通知文字描述
/*
// 可选,默认为"go_app",值可以为:
// "go_app": 打开应用
// "go_url": 跳转到URL
// "go_activity": 打开特定的activity
// "go_custom": 用户自定义内容。
*/
AfterOpen = "after_open"
Extra = "extra"
)
const (
Message = "message"
Notification = "notification"
)
const (
ExpireSpan = time.Second * 60 * 60 * 24
)
type SetPayload func(msg *UnicastMsg, option map[string]interface{}) error
type UmengNotification struct {
Auth *Auth
Request *httplib.BeegoHTTPRequest
}
func (notify *UmengNotification) Init(options ...push.Option) error {
return nil
}
func (notify *UmengNotification) Send(option map[string]interface{}) error {
var (
setPayload SetPayload = SetAndroidPayload
body []byte
err error
httpRsp *http.Response
ret = make(map[string]interface{})
data []byte
)
if option["DeviceType"].(int) == 1 {
setPayload = SetAndroidPayload
}
if body, err = notify.PostBody(option, setPayload); err != nil {
return err
}
notify.Request = httplib.Post(notify.Url(ApiSend, body))
notify.Request.Body(body)
if httpRsp, err = notify.Request.DoRequest(); err != nil {
return err
}
data, err = ioutil.ReadAll(httpRsp.Body)
defer httpRsp.Body.Close()
if err != nil {
return err
}
if err = json.Unmarshal(data, &ret); err != nil {
return err
}
if status, ok := ret["ret"]; ok {
if strings.EqualFold(fmt.Sprintf("%v", status), "SUCCESS") {
return nil
}
return fmt.Errorf("response:%v fail", ret)
}
return fmt.Errorf("response:%v invaild", ret)
}
func (notify *UmengNotification) PostBody(option map[string]interface{}, setPayload SetPayload) (body []byte, err error) {
var postData = new(UnicastMsg)
postData.AppKey = notify.Auth.AppKey
postData.Timestamp = fmt.Sprintf("%v", time.Now().Unix())
postData.ProductionMode = fmt.Sprintf("%v", true)
postData.Type = fmt.Sprintf("%v", option[Type])
postData.DeviceTokens = fmt.Sprintf("%v", option[DeviceTokens])
postData.Policy = MsgPolicy{ExpireTime: time.Now().Add(ExpireSpan).Format("2006-01-02 15:04:05")}
if err = setPayload(postData, option); err != nil {
return
}
postData.Description = fmt.Sprintf("%v", option[Title])
if body, err = json.Marshal(postData); err != nil {
return
}
return
}
func (notify *UmengNotification) Url(method string, body []byte) string {
var url string = fmt.Sprintf("%v%v", Host, method)
sign := fmt.Sprintf("%x", md5.Sum([]byte("POST"+url+string(body)+notify.Auth.AppMasterSecret)))
url = fmt.Sprintf("%v?sign=%v", url, sign)
return url
}
type UnicastMsg struct {
AppKey string `json:"appkey"`
Timestamp string `json:"timestamp"`
Type string `json:"type"` //发送类型 单播 列播...
ProductionMode string `json:"production_mode"`
DeviceTokens string `json:"device_tokens"`
Payload MsgPayload `json:"payload"`
Policy MsgPolicy `json:"policy"`
Description string `json:"description"`
}
type MsgPayload map[string]interface{}
type MsgPolicy struct {
ExpireTime string `json:"expire_time"`
}
//设置payload
func SetAndroidPayload(msg *UnicastMsg, option map[string]interface{}) error {
msg.Payload = make(map[string]interface{})
var (
displayType string
ok bool
)
if _, ok = option[DisplayType]; !ok {
return FiledErrorFormatFunc(DisplayType)
}
displayType = option[DisplayType].(string)
switch displayType {
case Message:
break
case Notification:
var payloadBody = make(map[string]interface{})
//if _,ok =option[Title];!ok{
// return FiledErrorFormatFunc(Title)
//}
//if _,ok =option[Text];!ok{
// return FiledErrorFormatFunc(Text)
//}
//if _,ok =option[Extra];ok{
// payloadBody[Extra]=option[Extra]
//}
payloadBody[Ticker] = option[Title]
payloadBody[Title] = option[Title]
payloadBody[Text] = option[Text]
payloadBody[AfterOpen] = "go_app"
msg.Payload[Body] = payloadBody
break
default:
return FiledErrorFormatFunc(DisplayType)
break
}
return nil
}
... ...
... ... @@ -90,6 +90,13 @@ func JsonUnmarshal(jsonData string, v interface{}) {
}
}
func JsonAssertString(v interface{}) string {
if data, e := json.Marshal(v); e == nil {
return string(data)
}
return ""
}
//深度拷贝
func DeepCopy(dst, src interface{}) error {
var buf bytes.Buffer
... ...
package protocol
/*PushInfo 推送信息*/
type PushInfoRequest struct {
Type int `json:"mmmType" valid:"Required"`
DeviceToken string `json:"deviceToken" valid:"Required"`
ClientId string `json:"clientId" valid:"Required"`
AppKey string `json:"appKey" valid:"Required"`
Secret string `json:"secret" valid:"Required"`
AppId string `json:"appId" valid:"Required"`
Title string `json:"title" valid:"Required"`
Content string `json:"content" valid:"Required"`
Ext map[string]interface{}
}
type PushInfoResponse struct {
}
... ...
... ... @@ -7,6 +7,14 @@ import (
func init() {
beego.GlobalControllerRouter["openapi/controllers/v1:PushController"] = append(beego.GlobalControllerRouter["openapi/controllers/v1:PushController"],
beego.ControllerComments{
Method: "PushInfo",
Router: `/pushInfo`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["openapi/controllers/v1:VodController"] = append(beego.GlobalControllerRouter["openapi/controllers/v1:VodController"],
beego.ControllerComments{
Method: "CreateUploadImage",
... ...
package push
import (
"gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
"openapi/internal/push"
"openapi/internal/push/getui"
"openapi/internal/push/umeng"
"openapi/internal/utils"
"openapi/protocol"
)
//推送信息
func PushInfo(header *protocol.RequestHeader, request *protocol.PushInfoRequest) (rsp *protocol.PushInfoResponse, err error) {
var (
sendData = make(map[string]interface{})
)
var pushService push.INotification = &getui.GetuiNotification{}
sendData["DeviceType"] = header.DeviceType
sendData[umeng.AppKey] = request.AppKey
sendData[umeng.DeviceTokens] = request.DeviceToken
sendData[umeng.DisplayType] = umeng.Notification
sendData[umeng.Title] = request.Title
sendData[umeng.Text] = request.Content
sendData[umeng.Extra] = request.Ext
sendData[umeng.Type] = "unicast"
err = pushService.Init(
push.DebugModule(true),
push.AppId(request.AppId),
push.AppKey(request.AppKey),
push.AppMasterSecret(request.Secret),
push.ClientId(request.ClientId),
push.MsgType(request.Type),
push.Title(request.Title),
push.Content(request.Content),
push.TransmissionContent(utils.JsonAssertString(request.Ext)),
)
if err != nil {
log.Error(err)
return
}
if err = pushService.Send(sendData); err != nil {
log.Error(err)
return
}
rsp = &protocol.PushInfoResponse{}
return
}
... ...