作者 yangfu

v0.0.3 feature multi channel support

## 推送调用说明
### 服务端调用
服务端 project_key 是 slave_key
存在子项目:
主项目键值 ability
子项目键值 worth
那么价值服务端调用推送接口,传入的project_key是worth
不存在子项目:
主项目键值 mmm.suplus.orders
子项目键值 -
那么订单服务端(消息中心)调用推送接口,传入的project_key是mmm.suplus.orders
### 客户端调用
app更新设备信息 project_key 是 master_key
例如:
同上,因为客户端主项目里面包含子项目,所以更新设备信息的时候传入主项目的编码,即为 ability,如果一个app只有一个项目 如:mmm.suplus.orders
\ No newline at end of file
... ...
... ... @@ -4,14 +4,18 @@ log_level = "${LOG_LEVEL||debug}"
aliyun_logs_access ="${aliyun_logs_access||F:/log/app.log}"
#阿里云基础配置
AccessKeyID ="LTAI4FhiZ3UktC6N1u3H5GFC"
AccessKeySecret ="UyspWwdni55CYQ02hUCint4qY2jNYO"
#个人阿里云基础配置
#AccessKeyID ="LTAI4FhiZ3UktC6N1u3H5GFC"
#AccessKeySecret ="UyspWwdni55CYQ02hUCint4qY2jNYO"
#OssEndPoint ="oss-cn-shanghai.aliyuncs.com"
#BuckName ="mmm-vod-dev-public"
cname ="https://media.goexample.live/"
OssEndPoint ="oss-cn-shanghai.aliyuncs.com"
BuckName ="mmm-vod-dev-public"
#公司
AccessKeyID ="LTAI4Fz1LUBW2fXp6QWaJHRS"
AccessKeySecret ="aLZXwK8pgrs10Ws03qcN7NsrSXFVsg"
OssEndPoint ="oss-cn-shenzhen.aliyuncs.com"
BuckName ="timeless-world"
#友盟推送
UMENG_API_HOST = "http://msg.umeng.com"
... ...
[dev_online]
#Ali could
AccessKeyID ="LTAI4Fz1LUBW2fXp6QWaJHRS"
AccessKeySecret ="aLZXwK8pgrs10Ws03qcN7NsrSXFVsg"
#日志
log_level = "${LOG_LEVEL||debug}"
aliyun_logs_access ="${aliyun_logs_access||F:/log/app.log}"
#AccessKeyID ="LTAI4FhiZ3UktC6N1u3H5GFC"
#AccessKeySecret ="UyspWwdni55CYQ02hUCint4qY2jNYO"
#OssEndPoint ="oss-cn-shanghai.aliyuncs.com"
#BuckName ="mmm-vod-dev-public"
#阿里云
AccessKeyID ="LTAI4Fz1LUBW2fXp6QWaJHRS"
AccessKeySecret ="aLZXwK8pgrs10Ws03qcN7NsrSXFVsg"
cname ="https://media.fjmaimaimai.com/"
OssEndPoint ="oss-cn-shenzhen.aliyuncs.com"
BuckName ="timeless-world"
#数据库相关
MYSQL_USER = "${MYSQL_USER||root}"
... ...
... ... @@ -9,6 +9,8 @@ AccessKeySecret ="aLZXwK8pgrs10Ws03qcN7NsrSXFVsg"
#阿里云
cname ="https://media.fjmaimaimai.com/"
OssEndPoint ="oss-cn-shenzhen.aliyuncs.com"
BuckName ="timeless-world"
#数据库相关
MYSQL_USER = "${MYSQL_USER||root}"
... ...
... ... @@ -9,6 +9,8 @@ AccessKeySecret ="aLZXwK8pgrs10Ws03qcN7NsrSXFVsg"
#阿里云
cname ="https://media.fjmaimaimai.com/"
OssEndPoint ="oss-cn-shenzhen.aliyuncs.com"
BuckName ="timeless-world"
#数据库相关
MYSQL_USER = "${MYSQL_USER||root1}"
... ...
... ... @@ -85,5 +85,5 @@ func CreateStsAuth(header *protocol.RequestHeader, files []string) (rsp interfac
FileName: fileBase,
})
}
return map[string]interface{}{"access": access, "files": listPath}, nil
return map[string]interface{}{"certificate": access, "files": listPath}, nil
}
... ...
... ... @@ -5,7 +5,7 @@ import (
"gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
protocol "openapi/pkg/domain"
"openapi/pkg/infrastructure/push"
"openapi/pkg/infrastructure/push/getui"
getui "openapi/pkg/infrastructure/push/getuiV2"
"openapi/pkg/infrastructure/repository"
"openapi/pkg/infrastructure/utils"
)
... ... @@ -47,6 +47,9 @@ func Notification(header *protocol.RequestHeader, request *protocol.PushInfoRequ
err = nil
return
}
if extInfo, ok := appInfo.GetExtInfo(); ok {
requestOriginal.Ext["intent"] = extInfo.Intent
}
if len(deviceList) == 0 {
err = protocol.NewSuccessWithMessage(fmt.Sprintf("接收人:%v 未查询到注册的设备信息!", request.Receivers))
return
... ... @@ -81,13 +84,16 @@ func NotificationOriginal(header *protocol.RequestHeader, request *protocol.Push
push.Title(request.Title),
push.Content(request.Content),
//push.TransmissionContent(utils.JsonAssertString(request.Ext)),
push.Extra(request.Ext),
}
)
rsp = &protocol.PushInfoResponse{}
if v, ok := request.Ext["transData"]; ok {
options = append(options, push.TransmissionContent(utils.JsonAssertString(v)))
}
if v, ok := request.Ext["intent"]; ok {
options = append(options, push.Intent(v.(string)))
}
clientIds = request.ClientIdList
switch len(clientIds) {
case 0:
... ... @@ -142,7 +148,7 @@ func UpdateDevice(header *protocol.RequestHeader, request *protocol.UpdateDevice
log.Error(err)
return
}
if err = rep.UpdateDevice(request.Muid, request.ClientId, request.DeviceToken, request.ProjectKey); err != nil {
if err = rep.UpdateDevice(request.Muid, request.ClientId, request.DeviceToken, request.ProjectKey, request.Phone); err != nil {
log.Error(err)
}
err = protocol.NewSuccessWithMessage("更新成功")
... ...
package constant
var (
// 主项目 键值
DefaultProjectKey = "mmm.ability"
// 子项目 键值
DefaultSlaveProjectKey = "mmm.ability.worth"
)
... ...
... ... @@ -5,6 +5,7 @@ var (
AccessKeyID string = "LTAI4FhiZ3UktC6N1u3H5GFC"
AccessKeySecret string = "UyspWwdni55CYQ02hUCint4qY2jNYO"
CName string = "https://media.goexample.live/"
RoleArn string = "acs:ram::1777936936207896:role/ossclientsts" //"acs:ram::1373671070046453:role/role-oss-sts"
)
func init() {
... ...
package domain
import (
"encoding/json"
"openapi/pkg/infrastructure/log"
)
/*PushInfo 推送信息*/
type PushInfoOriginalRequest struct {
Type int `json:"msgType"`
... ... @@ -35,6 +40,7 @@ type UpdateDeviceRequest struct {
Muid int64 `json:"muid" valid:"Required;"` //企业平台中的用户 UID
ClientId string `json:"clientId" valid:"Required"`
DeviceToken string `json:"deviceToken"`
Phone string `json:"phone"`
}
type UpdateDeviceResponse struct {
}
... ... @@ -54,4 +60,21 @@ type AppInfo struct {
AppMasterSecret string
AppId string
ProjectId int //项目编号
ExtInfo string
}
type ExtInfo struct {
Intent string `json:"intent"`
}
func (t *AppInfo) GetExtInfo() (*ExtInfo, bool) {
extInfo := &ExtInfo{}
if len(t.ExtInfo) == 0 {
return nil, false
}
if err := json.Unmarshal([]byte(t.ExtInfo), extInfo); err != nil {
log.Error(err.Error())
return nil, false
}
return extInfo, true
}
... ...
package aliyun
import (
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
"openapi/pkg/constant"
"openapi/pkg/infrastructure/log"
)
/*
设置参数。 指定角色的ARN。格式:acs:ram::$accountID:role/$roleName/$RoleSessionName 。 mmm-go@1373671070046453.onaliyun.com
配置用户 - ram角色权限-oss授权
$accountID 用户的阿里云账号
$RoleArn 点到ram角色 具体的权限详情 格式 格式:acs:ram::$accountID:role/$roleName/$RoleSessionName
1.使用授权账号(mmm-go@1373671070046453.onaliyun.com)登录
2.RAM访问控制->RAM角色管理->添加权限->为权限设置 oss访问权限
3.查看角色详情-》ARN( acs:ram::1373671070046453:role/role-oss-sts )需要配置给程序使用
4.RAM访问控制->用户(mmm-go@1373671070046453.onaliyun.com)->给用户设置角色
*/
/*
1.前端sts上传需要设置bucket跨域,bucket->权限管理->跨域设置
*/
func DefaultSts() (interface{}, error) {
//构建一个阿里云客户端, 用于发起请求。
//构建阿里云客户端时,需要设置AccessKey ID和AccessKey Secret。
... ... @@ -18,12 +32,7 @@ func DefaultSts() (interface{}, error) {
//构建请求对象。
request := sts.CreateAssumeRoleRequest()
request.Scheme = "https"
//设置参数。 指定角色的ARN。格式:acs:ram::$accountID:role/$roleName/$RoleSessionName 。 mmm-go@1373671070046453.onaliyun.com
// 配置用户 - ram角色权限-oss授权
// $accountID 用户的阿里云账号
// $RoleArn 点到ram角色 具体的权限详情 格式 格式:acs:ram::$accountID:role/$roleName/$RoleSessionName
request.RoleArn = fmt.Sprintf("acs:ram::1373671070046453:role/role-oss-sts")
request.RoleArn = constant.RoleArn
// 会话名称
request.RoleSessionName = "role-oss-sts-session"
... ... @@ -38,5 +47,7 @@ func DefaultSts() (interface{}, error) {
"expiration": response.Credentials.Expiration,
"accessKeyId": response.Credentials.AccessKeyId,
"securityToken": response.Credentials.SecurityToken,
"bucket": constant.BuckName,
"endpoint": constant.OssEndPoint,
}, nil
}
... ...
... ... @@ -6,7 +6,7 @@ import (
_ "github.com/go-sql-driver/mysql"
"gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
"openapi/pkg/constant"
_ "openapi/pkg/infrastructure/bgorm/model"
_ "openapi/pkg/infrastructure/bgorm/models"
)
func init() {
... ...
... ... @@ -15,6 +15,7 @@ type PushAppInfo struct {
AppMasterSecret string `orm:"column(app_master_secret);size(255);null" description:"推送服务端密钥"`
AppId string `orm:"column(app_id);size(255);null" description:"推送应用编号"`
ProjectId int `orm:"column(project_id);size(255);null" description:"项目编号"`
ExtInfo string `orm:"column(ext_info);size(1024);null" description:"扩展信息"`
}
func (t *PushAppInfo) TableName() string {
... ...
... ... @@ -9,6 +9,7 @@ import (
type PushDeviceInfo struct {
Id int `orm:"column(id);auto"`
Uid int64 `orm:"column(uid);null" description:"企业平台用户id (muid)"`
Phone string `orm:"column(phone);size(100);null" description:"设备手机号"`
ClientId string `orm:"column(client_id);size(100);null" description:"设备识别码 推送标识"`
DeviceToken string `orm:"column(device_token);size(100);null" description:"设备识别码 推送标识"`
CreateAt time.Time `orm:"column(create_at);type(timestamp);null" description:"创建时间"`
... ...
... ... @@ -51,6 +51,7 @@ type Message struct {
AppKey string `json:"appkey"`
IsOffline bool `json:"is_offline"`
MsgType string `json:"msgtype"`
OfflineExpireTime int `json:"offline_expire_time"` //offline_expire_time
}
//透传
... ... @@ -59,6 +60,7 @@ type Transmission struct {
TransmissionContent string `json:"transmission_content,omitempty"` //透传内容
DurationBegin string `json:"duration_begin,omitempty"`
DurationEnd string `json:"duration_end,omitempty"`
Notify interface{} `json:"notify,omitempty"`
}
func (o *Transmission) SetTransmissionType(t bool) {
... ... @@ -71,6 +73,27 @@ func (o *Transmission) SetDuration(begin, end string) {
o.DurationBegin = begin
o.DurationEnd = end
}
func (o *Transmission) SetNotify(options *push.Options) {
mapNotify := make(map[string]interface{})
mapNotify["title"] = options.Title
mapNotify["content"] = options.Content
// 安卓设备生成
mapNotify["intent"] = options.Intent
mapNotify["type"] = 1
//mapNotify["extKVList"] = []ExtKVList{
// //{Constrains: "HW", Key: "/message/android/notification/badge/add_num", Value: 1},
// //{Constrains: "HW", Key: "/message/android/notification/badge/class", Value: `"com.getui.demo.GetuiSdkDemoActivity"`},
// // 小米厂家支持
// {Constrains: "XM", Key: "channel", Value: "\"2882303761518034255\""},//Default 2882303761518034255
//}
o.Notify = mapNotify
}
type ExtKVList struct {
Constrains string `json:"constrains"`
Key string `json:"key"`
Value interface{} `json:"value"`
}
func NewTemplate(options *push.Options) *Template {
return &Template{
... ... @@ -87,6 +110,7 @@ func NewTransmission(options *push.Options) *Transmission {
}
t.SetTransmissionType(false)
t.SetTransmissionContent(options.TransmissionContent)
t.SetNotify(options)
return t
}
func NewMessage(options *push.Options) *Message {
... ... @@ -94,6 +118,7 @@ func NewMessage(options *push.Options) *Message {
AppKey: options.AppKey,
IsOffline: true,
MsgType: resolveMsgType(options.MsgType),
OfflineExpireTime: 100000000,
}
}
func resolveMsgType(msgType int) string {
... ... @@ -168,6 +193,10 @@ func NewAps(options *push.Options) (v *Aps) {
ContentAvailable: 0,
Sound: "default",
}
// 声音
if value, ok := options.GetExt("sound"); ok {
v.Sound = value.(string)
}
return
}
func NewAlert(options *push.Options) (v *Alert) {
... ... @@ -186,7 +215,7 @@ type Aps struct {
Alert *Alert `json:"alert"`
AutoBadge string `json:"autoBadge"` //用于计算应用上面未读数字
ContentAvailable int `json:"content-available,omitempty"` //推送直接带有透传数据 0:有通知栏消息 1:无通知栏消息
Sound string `json:"sound"`
Sound string `json:"sound"` // 推送声音 storein_voice.mp3
}
type Alert struct {
Title string `json:"title"`
... ...
package getuiV2
import (
"crypto/sha256"
"fmt"
"github.com/astaxie/beego/httplib"
"gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
"openapi/pkg/infrastructure/push"
"openapi/pkg/infrastructure/utils"
"strings"
"sync"
"time"
)
const (
host = "https://restapi.getui.com"
pushSingle = "push/single/cid"
saveListBody = "push/list/message"
pushList = "push/list/cid"
authSign = "auth"
)
var (
authtoken = ""
expire time.Time
authMux sync.RWMutex
expireSpan = time.Second * 600 //token 10分钟过期
)
const (
error_not_auth = "not_auth"
)
//GetuiNotification 个推消息推送
type GetuiNotification struct {
Options *push.Options
Request *httplib.BeegoHTTPRequest
retry int
}
func (notify *GetuiNotification) Init(options ...push.Option) error {
notify.Options = &push.Options{}
for _, o := range options {
o(notify.Options)
}
notify.retry = 3
return nil
}
func (notify *GetuiNotification) Send(option map[string]interface{}) (rsp map[string]interface{}, err error) {
retry := 1
for {
switch notify.Options.PushType {
case push.PushToSingle:
rsp, err = notify.pushToSingle(option)
case push.PushToList:
rsp, err = notify.pushToList(option)
default:
rsp, err = notify.pushToSingle(option)
}
if err == nil {
break
}
//重试
if err != nil && retry > notify.retry {
return
}
log.Error(fmt.Sprintf("【个推】 重试:%v 失败:%v", retry, err))
retry++
if retry > notify.retry {
break
}
}
return
}
//pushToSingle 单推
func (notify *GetuiNotification) pushToSingle(option map[string]interface{}) (rsp map[string]interface{}, err error) {
var token string
rsp = make(map[string]interface{})
if token, err = notify.GetAuthToken(); err != nil {
return
}
var (
result *MessageBase
url = notify.Url(notify.Options.AppId, pushSingle)
m = notify.Message(pushSingle)
)
notify.Request = httplib.Post(url)
notify.Request.Header("token", token)
notify.Request.JSONBody(m)
if err = notify.Request.ToJSON(&result); err != nil {
return
}
rsp = result.Data
notify.print(url, m, result, result)
if err = handleResult(url, result); err != nil {
return
}
return
}
//pushToList 群推
//步骤1.获取token
//步骤2.save_list_body保存消息共同体
//步骤3.push_list
func (notify *GetuiNotification) pushToList(option map[string]interface{}) (rsp map[string]interface{}, err error) {
var (
token string
taskId string
)
rsp = make(map[string]interface{})
if token, err = notify.GetAuthToken(); err != nil {
return
}
if taskId, err = notify.saveListBody(token, option); err != nil {
return
}
var (
result *MessageBase
url = notify.Url(notify.Options.AppId, pushList)
m = NewMapData()
)
m.AddFiled("audience.cid", notify.Options.ClientIds)
m.AddFiled("taskid", taskId)
m.AddFiled("is_async", true) //是否异步发送
notify.Request = httplib.Post(url)
notify.Request.Header("token", token)
notify.Request.JSONBody(m.Data)
if err = notify.Request.ToJSON(&result); err != nil {
return
}
rsp = result.Data
notify.print(url, m, result, result)
if err = handleResult(url, result); err != nil {
return
}
return
}
//saveListBody 保存消息共同体
func (notify *GetuiNotification) saveListBody(token string, option map[string]interface{}) (taskId string, err error) {
var (
result *MessageBase
url = notify.Url(notify.Options.AppId, saveListBody)
m = notify.Message(saveListBody)
)
notify.Request = httplib.Post(url)
notify.Request.Header("token", token)
notify.Request.JSONBody(m)
delete(m, "audience")
if err = notify.Request.ToJSON(&result); err != nil {
return
}
notify.print(url, m, result, result)
if err = handleResult(url, result); err != nil {
return
}
if id, ok := result.Data["taskid"]; ok {
taskId = id.(string)
return
}
return "", fmt.Errorf("error task id")
}
//Message 组装消息体
func (notify *GetuiNotification) Message(method string) map[string]interface{} {
msg := NewPushMessage(notify.Options)
return msg
}
//Url 组装请求地址
func (notify *GetuiNotification) Url(param string, method string) string {
return fmt.Sprintf("%v/v2/%v/%v", host, param, method)
}
//GetAuthToken 获取token
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)
if _, err = notify.Request.JSONBody(req); err != nil {
return
}
var rsp *AuthSignResponse
err = notify.Request.ToJSON(&rsp)
notify.print(url, req, rsp, rsp.MessageBase)
if err != nil {
return
}
if err = handleResult(url, rsp.MessageBase); err != nil {
return
}
authtoken = rsp.Data["token"].(string)
token = rsp.Data["token"].(string)
expire = time.Now().Add(rsp.GetExpireTime(expireSpan))
log.Info(fmt.Sprintf("【个推】token:%v expire:%v", token, expire))
return
}
//打印日志 debug_module=true print debug log
func (notify *GetuiNotification) print(url string, v interface{}, rsp interface{}, result *MessageBase) {
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.Msg))
}
//处理结果
func handleResult(url string, result *MessageBase) (err error) {
if result.Code == 0 {
return
}
switch result.Code {
case 0:
break
default:
setToken("")
break
}
err = fmt.Errorf("error:%v %v", result.Code, result.Msg)
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 setToken(token string) {
//authMux.Lock()
//defer authMux.Unlock()
authtoken = token
}
... ...
package getuiV2
import (
"openapi/pkg/infrastructure/push"
"openapi/pkg/infrastructure/utils"
"testing"
)
func TestGetui(t *testing.T) {
var param = make(map[string]interface{})
param["A"] = "A1"
param["B"] = 2
param["transData"] = struct{ Id int }{Id: 10}
notification := &GetuiNotification{}
err := notification.Init(
push.DebugModule(true),
push.AppId("TkpBI4awmg9fBUx3NWKXS6"),
push.AppKey("5AjJeDOSOZ5ojQpXJFjhg9"),
push.AppMasterSecret("9VnM8MaA6n84Y5VnOIaSvA"),
//单推
push.PushType(push.PushToSingle),
push.ClientId("b5fff5f6b0af551da5f381fa47991828"),
//群推
//push.PushType(push.PushToList),
//push.ClientIds([]string{"b5fff5f6b0af551da5f381fa47991828"}),
push.MsgType(push.SystemTransmission), //push.SystemNotification
push.Title("测试 hello"),
push.Content("hello content"),
push.TransmissionContent(utils.JsonAssertString(param["transData"])),
push.Extra(param),
)
if err != nil {
t.Fatal(err)
}
_, err = notification.Send(param)
if err != nil {
t.Fatal(err)
}
}
func TestGetuiPrd(t *testing.T) {
var param = make(map[string]interface{})
param["A"] = "A1"
param["B"] = 2
param["transData"] = struct {
Id int `json:"id"`
}{Id: 1}
notification := &GetuiNotification{}
err := notification.Init(
push.DebugModule(true),
push.AppId("WgrbaaStTk7JElrXOCgUg6"),
push.AppKey("FG5lbqVrHa5rS9NVfxNP7"),
push.AppMasterSecret("FW3jMNLJrRARYKv2iqA5H5"),
//单推
//push.PushType(push.PushToSingle),
//push.ClientId("502f4fd7ba5df15ac6b3d5c561efd9ca"),
//群推
push.PushType(push.PushToList),
push.ClientIds([]string{"502f4fd7ba5df15ac6b3d5c561efd9ca"}),
push.MsgType(push.SystemTransmission),
push.Title("hello"),
push.Content("hello content"),
push.TransmissionContent(utils.JsonAssertString(param["transData"])),
push.Extra(param),
)
if err != nil {
t.Fatal(err)
}
_, err = notification.Send(param)
if err != nil {
t.Fatal(err)
}
}
... ...
package getuiV2
import (
"gitlab.fjmaimaimai.com/mmm-go/gocomm/identity/uid"
"openapi/pkg/infrastructure/push"
"time"
)
//1.消息模板
type NotificationTemplate struct {
*Template
Notification *Notification `json:"notification"`
}
//1.新建通知模板
func NewNotificationTemplate(options *push.Options) *NotificationTemplate {
return &NotificationTemplate{
Template: NewTemplate(options),
Notification: &Notification{
Style: (&Style{}).SetStyle0(options),
Transmission: NewTransmission(options),
},
}
}
//2.透传模板
type TransmissionTemplate struct {
*Template
Transmission *Transmission `json:"transmission"`
PushInfo *PushInfo `json:"push_info"`
}
//2.新建透传模板
func NewTransmissionTemplate(options *push.Options) *TransmissionTemplate {
return &TransmissionTemplate{
Template: NewTemplate(options),
Transmission: NewTransmission(options),
PushInfo: NewPushInfo(options),
}
}
type Template struct {
ClientId string `json:"cid,omitempty"`
RequestId string `json:"requestid,omitempty"`
Message *Message `json:"message"`
}
type Notification struct {
Style *Style `json:"style"`
*Transmission
}
type Message struct {
AppKey string `json:"appkey"`
IsOffline bool `json:"is_offline"`
MsgType string `json:"msgtype"`
OfflineExpireTime int `json:"offline_expire_time"` //offline_expire_time
}
//透传
type Transmission struct {
TransmissionType bool `json:"transmission_type"` //收到消息是否立即启动应用,true为立即启动,false则广播等待启动,默认是否
TransmissionContent string `json:"transmission_content,omitempty"` //透传内容
DurationBegin string `json:"duration_begin,omitempty"`
DurationEnd string `json:"duration_end,omitempty"`
Notify interface{} `json:"notify,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
}
func (o *Transmission) SetNotify(options *push.Options) {
mapNotify := make(map[string]interface{})
mapNotify["title"] = options.Title
mapNotify["content"] = options.Content
// 安卓设备生成
mapNotify["intent"] = options.Intent
mapNotify["type"] = 1
//mapNotify["extKVList"] = []ExtKVList{
// //{Constrains: "HW", Key: "/message/android/notification/badge/add_num", Value: 1},
// //{Constrains: "HW", Key: "/message/android/notification/badge/class", Value: `"com.getui.demo.GetuiSdkDemoActivity"`},
// // 小米厂家支持
// {Constrains: "XM", Key: "channel", Value: "\"2882303761518034255\""},//Default 2882303761518034255
//}
o.Notify = mapNotify
}
type ExtKVList struct {
Constrains string `json:"constrains"`
Key string `json:"key"`
Value interface{} `json:"value"`
}
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(false)
t.SetTransmissionContent(options.TransmissionContent)
t.SetNotify(options)
return t
}
func NewMessage(options *push.Options) *Message {
return &Message{
AppKey: options.AppKey,
IsOffline: true,
MsgType: resolveMsgType(options.MsgType),
OfflineExpireTime: 100000000,
}
}
func resolveMsgType(msgType int) string {
/*
消息应用类型,
可选项:notification、link、notypopload、startactivity, transmission
*/
switch msgType {
case push.SystemNotification:
return "notification"
case push.SystemTransmission:
return "transmission"
}
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不振动。默认振动
NotifyId int `json:"notify_id"` //需要被覆盖的消息已经增加了notifyId字段,用于实现下发消息的覆盖。新的消息使用相同的notifyId下发。
}
//设置默认样式 0
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地址
s.NotifyId = 1
return s
}
//认证请求/应答
type AuthSignRequest struct {
Sign string `json:"sign"`
Timestamp string `json:"timestamp"`
AppKey string `json:"appkey"`
}
type AuthSignResponse struct {
*Result
*MessageBase
//ExpireTime string `json:"expire_time"`
//AuthToken string `json:"token"`
}
func (auth AuthSignResponse) GetExpireTime(defaultEx time.Duration) time.Duration {
//v,e := strconv.Atoi(auth.ExpireTime)
//if e!=nil || v==0{
return defaultEx
//}
//return time.Duration(v)*time.Second
}
type MessageBase struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data map[string]interface{} `json:"data"`
}
//应答结果
type Result struct {
Result string `json:"result"`
TaskId string `json:"taskid"`
Status string `json:"status"`
Desc string `json:"desc"`
}
//透传附加的推送信息
func NewPushInfo(options *push.Options) (v *PushInfo) {
v = &PushInfo{
Aps: NewAps(options),
Payload: options.TransmissionContent,
}
return
}
func NewAps(options *push.Options) (v *Aps) {
v = &Aps{
Alert: NewAlert(options),
AutoBadge: "+1",
ContentAvailable: 0,
Sound: "default",
}
// 声音
if value, ok := options.GetExt("sound"); ok {
v.Sound = value.(string)
}
return
}
func NewAlert(options *push.Options) (v *Alert) {
v = &Alert{
//Title: options.Title, //TODO:去掉这个ios通知栏只有内容行,没有标题行,如果后期需要显示这个 需要ext里面扩展字段用来控制是否显示标题
Body: options.Content,
}
return
}
type PushInfo struct {
Aps *Aps `json:"aps"`
Payload string `json:"payload,omitempty"`
}
type Aps struct {
Alert *Alert `json:"alert"`
AutoBadge string `json:"autoBadge"` //用于计算应用上面未读数字
ContentAvailable int `json:"content-available,omitempty"` //推送直接带有透传数据 0:有通知栏消息 1:无通知栏消息
Sound string `json:"sound"` // 推送声音 storein_voice.mp3
}
type Alert struct {
Title string `json:"title"`
Body string `json:"body"`
}
... ...
package getuiV2
import (
"openapi/pkg/infrastructure/push"
"reflect"
"strings"
)
const (
splitChar = "."
contentTemplate = "点击查看详情"
)
type MapData struct {
Data map[string]interface{}
}
func NewMapData() *MapData {
return &MapData{
Data: make(map[string]interface{}),
}
}
func (m *MapData) AddFiled(field string, value interface{}) *MapData {
fields := strings.Split(field, splitChar)
var cur map[string]interface{}
cur = m.Data
for index, f := range fields {
if index != (len(fields) - 1) {
if _, ok := cur[f]; !ok {
cur[f] = make(map[string]interface{})
}
cur = cur[f].(map[string]interface{})
continue
}
if _, ok := cur[f]; !ok {
cur[f] = value
}
}
return m
}
func (m *MapData) GetFiledMap(field string) map[string]interface{} {
fields := strings.Split(field, splitChar)
cur := m.Data
for _, f := range fields {
if _, ok := cur[f]; !ok {
cur[f] = make(map[string]interface{})
}
cur = cur[f].(map[string]interface{})
}
return cur
}
func (m *MapData) SetFieldMap(fieldMap map[string]interface{}, field string, value interface{}) *MapData {
if value == nil {
return m
}
v := reflect.ValueOf(value)
if !v.IsValid() {
return m
}
if v.IsZero() {
return m
}
fieldMap[field] = value
return m
}
// 推送消息
func NewPushMessage(option *push.Options) map[string]interface{} {
m := NewMapData()
// request_id
m.AddFiled("request_id", genRequestId())
// setting
m.AddFiled("settings.ttl", 3600*24)
// audience
m.AddFiled("audience.cid", []string{option.ClientId})
if len(option.ClientIds) > 0 {
m.AddFiled("audience.cid", option.ClientIds)
}
// push_message
if option.MsgType == push.Notification {
pushMessageNotification(m, option)
} else if option.MsgType == push.SystemTransmission {
pushMessageTransmission(m, option)
}
// push_channel
channelAndroid(m, option)
channelIOS(m, option)
return m.Data
}
/*push_message*/
func pushMessageNotification(m *MapData, option *push.Options) {
notification := m.GetFiledMap("push_message.notification")
m.SetFieldMap(notification, "title", option.Title)
m.SetFieldMap(notification, "body", option.Content)
m.SetFieldMap(notification, "click_type", "payload")
m.SetFieldMap(notification, "payload", option.TransmissionContent)
if len(option.Intent) > 0 {
m.SetFieldMap(notification, "click_type", "intent")
m.SetFieldMap(notification, "intent", option.Intent)
}
}
func pushMessageTransmission(m *MapData, option *push.Options) {
m.AddFiled("push_message.transmission", option.TransmissionContent)
}
/*channel*/
/*
"android":{
"ups":{
"notification":{
"title":"请填写android标题",
"body":"请填写android内容",
"click_type":"url",
"url":"https://xxx"
}
}
},
*/
func channelAndroid(m *MapData, option *push.Options) {
notification := m.GetFiledMap("push_channel.android.ups.notification")
m.SetFieldMap(notification, "title", option.Title).
SetFieldMap(notification, "body", contentTemplate) //TODO:配置控制body是否展示
if len(option.Intent) > 0 {
m.SetFieldMap(notification, "intent", option.FormatTranDataToIntent()).
SetFieldMap(notification, "click_type", "intent")
} else {
m.SetFieldMap(notification, "click_type", "payload")
m.SetFieldMap(notification, "payload", option.TransmissionContent)
}
}
/*
"ios":{
"type":"notify",
"payload":"自定义消息",
"aps":{
"alert":{
"title":"请填写ios标题",
"body":"请填写ios内容"
},
"content-available":0
},
"auto_badge":"+1"
}
*/
func channelIOS(m *MapData, option *push.Options) {
m.AddFiled("push_channel.ios.type", "notify")
m.AddFiled("push_channel.ios.payload", option.TransmissionContent)
alert := m.GetFiledMap("push_channel.ios.aps.alert")
//TODO:去掉这个ios通知栏只有内容行,没有标题行,如果后期需要显示这个 需要ext里面扩展字段用来控制是否显示标题
//m.SetFieldMap(alert, "title", option.Title)
m.SetFieldMap(alert, "body", option.Content)
m.AddFiled("push_channel.ios.aps.content-available", 0)
if v, ok := option.GetExt("sound"); ok && len(v.(string)) > 0 {
m.AddFiled("push_channel.ios.aps.sound", v)
}
m.AddFiled("push_channel.ios.auto_badge", "+1")
}
... ...
package getuiV2
import (
"gitlab.fjmaimaimai.com/mmm-go/gocomm/common"
"testing"
)
func TestNewMapData(t *testing.T) {
m := NewMapData()
m.AddFiled("user.id", 1)
m.AddFiled("user.name", "tip")
m.AddFiled("user.sex", true)
m.AddFiled("address.lon", 59.2156461)
m.AddFiled("address.lat", 23.1245648)
m.AddFiled("phone", "18860183050")
notification := m.GetFiledMap("notification")
notification["title"] = "xxx"
notification["body"] = "body"
m.SetFieldMap(notification, "url", "http://")
m.SetFieldMap(notification, "options", nil)
t.Log(common.AssertJson(m.Data))
}
... ...
package push
import (
"bytes"
"fmt"
"strings"
)
type Options struct {
AppId string
AppKey string
... ... @@ -13,9 +19,12 @@ type Options struct {
Title string
Content string
Extra interface{} //扩展数据
Extra interface{} //扩展数据 map[string]interface{} key:"sound" storein_voice.mp3
TransmissionContent string //透传内容
//多厂家支持
Intent string
DebugModule bool
}
type Option func(o *Options)
... ... @@ -95,12 +104,48 @@ func TransmissionContent(content string) Option {
o.TransmissionContent = content
}
}
func Intent(intent string) Option {
return func(o *Options) {
o.Intent = intent
}
}
func DebugModule(module bool) Option {
return func(o *Options) {
o.DebugModule = module
}
}
func (o *Options) GetExt(key string) (value interface{}, ok bool) {
var mapExt map[string]interface{}
if mapExt, ok = o.Extra.(map[string]interface{}); !ok {
return
}
if value, ok = mapExt[key]; ok {
return
}
return
}
func (o *Options) FormatTranDataToIntent() string {
tran, ok := o.GetExt("transData")
if !ok {
return o.Intent
}
var tranMap map[string]interface{}
tranMap, ok = tran.(map[string]interface{})
if !ok {
return o.Intent
}
var params = bytes.NewBuffer(nil)
for k, v := range tranMap {
params.WriteString(fmt.Sprintf("S.%s=%v;", k, v))
}
if idx := strings.Index(o.Intent, "end"); idx > 0 {
return o.Intent[0:idx] + params.String() + "end"
}
return o.Intent
}
const (
Message = iota + 1
Notification
... ...
... ... @@ -4,7 +4,7 @@ import (
"fmt"
"github.com/astaxie/beego/orm"
"openapi/pkg/domain"
"openapi/pkg/infrastructure/bgorm/model"
"openapi/pkg/infrastructure/bgorm/models"
. "openapi/pkg/infrastructure/utils"
)
... ...
... ... @@ -3,7 +3,7 @@ package repository
import (
"github.com/astaxie/beego/orm"
"openapi/pkg/domain"
"openapi/pkg/infrastructure/bgorm/model"
"openapi/pkg/infrastructure/bgorm/models"
"sync"
)
... ... @@ -44,6 +44,7 @@ func (repository *AppInfoRepository) transformBgormModelToDomainModel(model *mod
AppMasterSecret: model.AppMasterSecret,
AppId: model.AppId,
ProjectId: model.ProjectId,
ExtInfo: model.ExtInfo,
}, nil
}
... ...
... ... @@ -3,7 +3,7 @@ package repository
import (
"github.com/astaxie/beego/orm"
"openapi/pkg/domain"
"openapi/pkg/infrastructure/bgorm/model"
"openapi/pkg/infrastructure/bgorm/models"
"openapi/pkg/infrastructure/utils"
"strings"
"time"
... ... @@ -18,10 +18,12 @@ func (repository *PushDeviceRepository) Save(device *domain.UpdateDeviceRequest)
m := &models.PushDeviceInfo{
Uid: device.Muid,
ClientId: strings.TrimSpace(device.ClientId),
Phone: device.Phone,
DeviceToken: strings.TrimSpace(device.DeviceToken),
CreateAt: time.Now(),
UpdateAt: time.Now(),
ProjectMasterKey: device.ProjectKey,
IsActive: 1,
}
_, err := o.Insert(m)
return err
... ... @@ -59,17 +61,17 @@ func (repository *PushDeviceRepository) Find(queryOptions map[string]interface{}
return
}
func (repository *PushDeviceRepository) UpdateDevice(uid int64, clientId, deviceToken string, projectKey string) error {
func (repository *PushDeviceRepository) UpdateDevice(uid int64, clientId, deviceToken string, projectKey string, phone string) error {
o := orm.NewOrm()
o.Begin()
//更新其他绑定这个client_id的设备 is_active=0
//更新(这个项目)其他绑定这个client_id的设备 is_active=0
_, err := o.Raw("UPDATE push_device_info SET update_at=now(),is_active=0 where client_id=? and is_active=1 and project_master_key=?", clientId, projectKey).Exec()
if err != nil {
o.Rollback()
return err
}
_, err = o.Raw("UPDATE push_device_info SET client_id=?,device_token = ?,update_at=now(),is_active=1 where uid=? and project_master_key=?", clientId, deviceToken, uid, projectKey).Exec()
_, err = o.Raw("UPDATE push_device_info SET client_id=?,device_token = ?,update_at=now(),is_active=1,phone=? where uid=? and project_master_key=?", clientId, deviceToken, phone, uid, projectKey).Exec()
if err != nil {
o.Rollback()
return err
... ...
... ... @@ -32,8 +32,8 @@ func (this *PushController) PushInfo() {
msg = m
return
}
if len(request.ProjectKey) == 0 {
request.ProjectKey = "worth" //默认是价值项目
if len(request.ProjectKey) == 0 || request.ProjectKey == "worth" {
request.ProjectKey = "mmm.ability.worth" //默认是价值项目
}
header := controllers.GetRequestHeader(this.Ctx)
msg = protocol.NewReturnResponse(push.Notification(header, request))
... ... @@ -78,7 +78,7 @@ func (this *PushController) UpdateDevice() {
return
}
if request.ProjectKey == "" {
request.ProjectKey = "ability" //默认能力展示项目
request.ProjectKey = "mmm.ability" //默认能力展示项目
}
header := controllers.GetRequestHeader(this.Ctx)
msg = protocol.NewReturnResponse(push.UpdateDevice(header, request))
... ...