diff --git a/src/push/Makefile b/src/push/Makefile new file mode 100644 index 000000000..73f1fdd35 --- /dev/null +++ b/src/push/Makefile @@ -0,0 +1,27 @@ +.PHONY: all build run gotool install clean help + +BINARY_NAME=open_im_push +BIN_DIR=../../bin/ +LAN_FILE=.go +GO_FILE:=${BINARY_NAME}${LAN_FILE} + +all: gotool build + +build: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ${BINARY_NAME} ${GO_FILE} + +run: + @go run ./ + +gotool: + go fmt ./ + go vet ./ + +install: + make build + mv ${BINARY_NAME} ${BIN_DIR} + +clean: + @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi + + diff --git a/src/push/logic/init.go b/src/push/logic/init.go new file mode 100644 index 000000000..adcd92964 --- /dev/null +++ b/src/push/logic/init.go @@ -0,0 +1,66 @@ +/* +** description(""). +** copyright('open-im,www.open-im.io'). +** author("fg,Gordon@open-im.io"). +** time(2021/3/22 15:33). + */ +package logic + +import ( + "Open_IM/src/common/config" + "Open_IM/src/common/db" + "Open_IM/src/common/db/mysql_model/im_mysql_model" + "Open_IM/src/common/kafka" + "Open_IM/src/common/log" + "Open_IM/src/utils" + "time" +) + +var ( + rpcServer RPCServer + pushCh PushConsumerHandler + pushTerminal []int32 + producer *kafka.Producer +) + +func Init(rpcPort int) { + log.NewPrivateLog(config.Config.ModuleName.PushName) + rpcServer.Init(rpcPort) + pushCh.Init() + pushTerminal = []int32{utils.IOSPlatformID} +} +func init() { + producer = kafka.NewKafkaProducer(config.Config.Kafka.Ws2mschat.Addr, config.Config.Kafka.Ws2mschat.Topic) +} + +func Run() { + go rpcServer.run() + go scheduleDelete() + go pushCh.pushConsumerGroup.RegisterHandleAndConsumer(&pushCh) +} + +func scheduleDelete() { + //uid, _ := im_mysql_model.SelectAllUID() + //db.DB.DelHistoryChat(0, uid) + //log.Info("", "", "sssssssssss") + //if err != nil { + // db.DB.DelHistoryChat(0, uid) + //} + + for { + now := time.Now() + // 计算下一个零点 + next := now.Add(time.Hour * 24) + next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location()) + t := time.NewTimer(next.Sub(now)) + <-t.C + + uid, err := im_mysql_model.SelectAllUID() + if err != nil { + db.DB.DelHistoryChat(int64(config.Config.Mongo.DBRetainChatRecords), uid) + } + + //以下为定时执行的操作 + scheduleDelete() + } +} diff --git a/src/push/logic/push_handler.go b/src/push/logic/push_handler.go new file mode 100644 index 000000000..ae4cc6b4f --- /dev/null +++ b/src/push/logic/push_handler.go @@ -0,0 +1,65 @@ +/* +** description(""). +** copyright('Open_IM,www.Open_IM.io'). +** author("fg,Gordon@tuoyun.net"). +** time(2021/5/13 10:33). + */ +package logic + +import ( + "Open_IM/src/common/config" + kfk "Open_IM/src/common/kafka" + "Open_IM/src/common/log" + pbChat "Open_IM/src/proto/chat" + pbRelay "Open_IM/src/proto/relay" + "github.com/Shopify/sarama" + "github.com/golang/protobuf/proto" +) + +type fcb func(msg []byte) + +type PushConsumerHandler struct { + msgHandle map[string]fcb + pushConsumerGroup *kfk.MConsumerGroup +} + +func (ms *PushConsumerHandler) Init() { + ms.msgHandle = make(map[string]fcb) + ms.msgHandle[config.Config.Kafka.Ms2pschat.Topic] = ms.handleMs2PsChat + ms.pushConsumerGroup = kfk.NewMConsumerGroup(&kfk.MConsumerGroupConfig{KafkaVersion: sarama.V0_10_2_0, + OffsetsInitial: sarama.OffsetNewest, IsReturnErr: false}, []string{config.Config.Kafka.Ms2pschat.Topic}, config.Config.Kafka.Ms2pschat.Addr, + config.Config.Kafka.ConsumerGroupID.MsgToPush) +} +func (ms *PushConsumerHandler) handleMs2PsChat(msg []byte) { + log.InfoByKv("msg come from kafka And push!!!", "", "msg", string(msg)) + pbData := pbChat.MsgSvrToPushSvrChatMsg{} + if err := proto.Unmarshal(msg, &pbData); err != nil { + log.ErrorByKv("push Unmarshal msg err", "", "msg", string(msg), "err", err.Error()) + return + } + sendPbData := pbRelay.MsgToUserReq{} + sendPbData.SendTime = pbData.SendTime + sendPbData.OperationID = pbData.OperationID + sendPbData.ServerMsgID = pbData.MsgID + sendPbData.MsgFrom = pbData.MsgFrom + sendPbData.ContentType = pbData.ContentType + sendPbData.SessionType = pbData.SessionType + sendPbData.RecvID = pbData.RecvID + sendPbData.Content = pbData.Content + sendPbData.SendID = pbData.SendID + sendPbData.PlatformID = pbData.PlatformID + sendPbData.RecvSeq = pbData.RecvSeq + sendPbData.IsEmphasize = pbData.IsEmphasize + //Call push module to send message to the user + MsgToUser(&sendPbData, pbData.OfflineInfo, pbData.Options) +} +func (PushConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil } +func (PushConsumerHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil } +func (ms *PushConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, + claim sarama.ConsumerGroupClaim) error { + for msg := range claim.Messages() { + log.InfoByKv("kafka get info to mysql", "", "msgTopic", msg.Topic, "msgPartition", msg.Partition, "msg", string(msg.Value)) + ms.msgHandle[msg.Topic](msg.Value) + } + return nil +} diff --git a/src/push/logic/push_rpc_server.go b/src/push/logic/push_rpc_server.go new file mode 100644 index 000000000..acec92601 --- /dev/null +++ b/src/push/logic/push_rpc_server.go @@ -0,0 +1,71 @@ +package logic + +import ( + "Open_IM/src/common/config" + "Open_IM/src/common/log" + "Open_IM/src/proto/push" + pbRelay "Open_IM/src/proto/relay" + "Open_IM/src/utils" + "context" + "github.com/skiffer-git/grpc-etcdv3/getcdv3" + "google.golang.org/grpc" + "net" + "strings" +) + +type RPCServer struct { + rpcPort int + rpcRegisterName string + etcdSchema string + etcdAddr []string +} + +func (r *RPCServer) Init(rpcPort int) { + r.rpcPort = rpcPort + r.rpcRegisterName = config.Config.RpcRegisterName.OpenImPushName + r.etcdSchema = config.Config.Etcd.EtcdSchema + r.etcdAddr = config.Config.Etcd.EtcdAddr +} +func (r *RPCServer) run() { + ip := utils.ServerIP + registerAddress := ip + ":" + utils.IntToString(r.rpcPort) + listener, err := net.Listen("tcp", registerAddress) + if err != nil { + log.ErrorByKv("push module rpc listening port err", "", "err", err.Error()) + return + } + defer listener.Close() + srv := grpc.NewServer() + defer srv.GracefulStop() + pbPush.RegisterPushMsgServiceServer(srv, r) + err = getcdv3.RegisterEtcd(r.etcdSchema, strings.Join(r.etcdAddr, ","), ip, r.rpcPort, r.rpcRegisterName, 10) + if err != nil { + log.ErrorByKv("register push module rpc to etcd err", "", "err", err.Error()) + } + err = srv.Serve(listener) + if err != nil { + log.ErrorByKv("push module rpc start err", "", "err", err.Error()) + return + } +} +func (r *RPCServer) PushMsg(_ context.Context, pbData *pbPush.PushMsgReq) (*pbPush.PushMsgResp, error) { + sendPbData := pbRelay.MsgToUserReq{} + sendPbData.SendTime = pbData.SendTime + sendPbData.OperationID = pbData.OperationID + sendPbData.ServerMsgID = pbData.MsgID + sendPbData.MsgFrom = pbData.MsgFrom + sendPbData.ContentType = pbData.ContentType + sendPbData.SessionType = pbData.SessionType + sendPbData.RecvID = pbData.RecvID + sendPbData.Content = pbData.Content + sendPbData.SendID = pbData.SendID + sendPbData.PlatformID = pbData.PlatformID + sendPbData.RecvSeq = pbData.RecvSeq + sendPbData.IsEmphasize = pbData.IsEmphasize + //Call push module to send message to the user + MsgToUser(&sendPbData, pbData.OfflineInfo, pbData.Options) + return &pbPush.PushMsgResp{ + ResultCode: 0, + }, nil + +} diff --git a/src/push/logic/push_to_client.go b/src/push/logic/push_to_client.go new file mode 100644 index 000000000..d53f459e2 --- /dev/null +++ b/src/push/logic/push_to_client.go @@ -0,0 +1,112 @@ +/* +** description(""). +** copyright('open-im,www.open-im.io'). +** author("fg,Gordon@open-im.io"). +** time(2021/3/5 14:31). + */ +package logic + +import ( + "Open_IM/src/common/config" + "Open_IM/src/common/constant" + "Open_IM/src/common/log" + pbChat "Open_IM/src/proto/chat" + pbRelay "Open_IM/src/proto/relay" + pbGetInfo "Open_IM/src/proto/user" + rpcChat "Open_IM/src/rpc/chat/chat" + "Open_IM/src/rpc/user/internal_service" + "Open_IM/src/utils" + "context" + "encoding/json" + "fmt" + "github.com/skiffer-git/grpc-etcdv3/getcdv3" + "strings" +) + +type EChatContent struct { + SessionType int `json:"chatType"` + From string `json:"from"` + To string `json:"to"` + Seq int64 `json:"seq"` +} + +func MsgToUser(sendPbData *pbRelay.MsgToUserReq, OfflineInfo, Options string) { + var wsResult []*pbRelay.SingleMsgToUser + isShouldOfflinePush := true + MOptions := utils.JsonStringToMap(Options) + isOfflinePush := utils.GetSwitchFromOptions(MOptions, "offlinePush") + log.InfoByKv("Get chat from msg_transfer And push chat", sendPbData.OperationID, "PushData", sendPbData) + grpcCons := getcdv3.GetConn4Unique(config.Config.Etcd.EtcdSchema, strings.Join(config.Config.Etcd.EtcdAddr, ","), config.Config.RpcRegisterName.OpenImOnlineMessageRelayName) + //Online push message + for _, v := range grpcCons { + msgClient := pbRelay.NewOnlineMessageRelayServiceClient(v) + reply, err := msgClient.MsgToUser(context.Background(), sendPbData) + if reply != nil && reply.Resp != nil && err == nil { + wsResult = append(wsResult, reply.Resp...) + } + } + if isOfflinePush && sendPbData.ContentType != constant.SyncSenderMsg { + + for _, t := range pushTerminal { + for _, v := range wsResult { + if v.RecvPlatFormID == t && v.ResultCode == 0 { + isShouldOfflinePush = false + break + } + } + if isShouldOfflinePush { + //Use offline push messaging + var UIDList []string + UIDList = append(UIDList, sendPbData.RecvID) + var sendUIDList []string + sendUIDList = append(sendUIDList, sendPbData.SendID) + userInfo, err := internal_service.GetUserInfoClient(&pbGetInfo.GetUserInfoReq{UserIDList: sendUIDList, OperationID: sendPbData.OperationID}) + if err != nil { + log.ErrorByArgs(fmt.Sprintf("err=%v,call GetUserInfoClient rpc server failed", err)) + return + } + + customContent := EChatContent{ + SessionType: int(sendPbData.SessionType), + From: sendPbData.SendID, + To: sendPbData.RecvID, + Seq: sendPbData.RecvSeq, + } + bCustomContent, _ := json.Marshal(customContent) + + jsonCustomContent := string(bCustomContent) + switch sendPbData.ContentType { + case constant.Text: + IOSAccountListPush(UIDList, userInfo.Data[0].Name, sendPbData.Content, jsonCustomContent) + case constant.Picture: + IOSAccountListPush(UIDList, userInfo.Data[0].Name, constant.ContentType2PushContent[constant.Picture], jsonCustomContent) + case constant.Voice: + IOSAccountListPush(UIDList, userInfo.Data[0].Name, constant.ContentType2PushContent[constant.Voice], jsonCustomContent) + case constant.Video: + IOSAccountListPush(UIDList, userInfo.Data[0].Name, constant.ContentType2PushContent[constant.Video], jsonCustomContent) + case constant.File: + IOSAccountListPush(UIDList, userInfo.Data[0].Name, constant.ContentType2PushContent[constant.File], jsonCustomContent) + default: + + } + + } else { + isShouldOfflinePush = true + } + } + + } + +} + +func SendMsgByWS(m *pbChat.WSToMsgSvrChatMsg) { + m.MsgID = rpcChat.GetMsgID(m.SendID) + pid, offset, err := producer.SendMessage(m, m.SendID) + if err != nil { + log.ErrorByKv("sys send msg to kafka failed", m.OperationID, "send data", m.String(), "pid", pid, "offset", offset, "err", err.Error(), "msgKey--sendID", m.SendID) + } + pid, offset, err = producer.SendMessage(m, m.RecvID) + if err != nil { + log.ErrorByKv("kafka send failed", m.OperationID, "send data", m.String(), "pid", pid, "offset", offset, "err", err.Error(), "msgKey--recvID", m.RecvID) + } +} diff --git a/src/push/logic/tpns.go b/src/push/logic/tpns.go new file mode 100644 index 000000000..616c9c099 --- /dev/null +++ b/src/push/logic/tpns.go @@ -0,0 +1,34 @@ +package logic + +import ( + "Open_IM/src/common/config" + tpns "Open_IM/src/push/sdk/tpns-server-sdk-go/go" + "Open_IM/src/push/sdk/tpns-server-sdk-go/go/auth" + "Open_IM/src/push/sdk/tpns-server-sdk-go/go/common" + "Open_IM/src/push/sdk/tpns-server-sdk-go/go/req" +) + +var badgeType = -2 +var iosAcceptId = auth.Auther{AccessID: config.Config.Push.Tpns.Ios.AccessID, SecretKey: config.Config.Push.Tpns.Ios.SecretKey} + +func IOSAccountListPush(accounts []string, title, content, jsonCustomContent string) { + var iosMessage = tpns.Message{ + Title: title, + Content: content, + IOS: &tpns.IOSParams{ + Aps: &tpns.Aps{ + BadgeType: &badgeType, + Sound: "default", + Category: "INVITE_CATEGORY", + }, + CustomContent: jsonCustomContent, + //CustomContent: `"{"key\":\"value\"}"`, + }, + } + pushReq, reqBody, err := req.NewListAccountPush(accounts, iosMessage) + if err != nil { + return + } + iosAcceptId.Auth(pushReq, auth.UseSignAuthored, iosAcceptId, reqBody) + common.PushAndGetResult(pushReq) +} diff --git a/src/push/open_im_push.go b/src/push/open_im_push.go new file mode 100644 index 000000000..159ecc6cb --- /dev/null +++ b/src/push/open_im_push.go @@ -0,0 +1,17 @@ +package main + +import ( + "Open_IM/src/push/logic" + "flag" + "sync" +) + +func main() { + rpcPort := flag.Int("port", -1, "rpc listening port") + flag.Parse() + var wg sync.WaitGroup + wg.Add(1) + logic.Init(*rpcPort) + logic.Run() + wg.Wait() +} diff --git a/src/push/sdk/tpns-server-sdk-go/go/auth/auth.go b/src/push/sdk/tpns-server-sdk-go/go/auth/auth.go new file mode 100644 index 000000000..5b8a486b0 --- /dev/null +++ b/src/push/sdk/tpns-server-sdk-go/go/auth/auth.go @@ -0,0 +1,62 @@ +package auth + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + b64 "encoding/base64" + "encoding/hex" + "fmt" + "net/http" + "strconv" + "time" +) + +type Auther struct { + AccessID string + SecretKey string +} + +var UseSignAuthored = true + +func (a *Auther) Auth(req *http.Request, useSignAuthored bool, auth Auther, reqBody string) { + + if useSignAuthored { + now := time.Now() + timeStamp := now.Unix() + req.Header.Add("AccessId", auth.AccessID) + req.Header.Add("TimeStamp", strconv.Itoa(int(timeStamp))) + sign := GenSign(uint64(timeStamp), auth.AccessID, auth.SecretKey, reqBody) + req.Header.Add("Sign", sign) + } else { + author := makeAuthHeader(a.AccessID, a.SecretKey) + //log.Printf("author string:%v", author) + req.Header.Add("Authorization", author) + } + //req.Header.Add("Content-Type", "application/json") +} + +func makeAuthHeader(appID, secretKey string) string { + base64Str := base64.StdEncoding.EncodeToString( + []byte( + fmt.Sprintf("%s:%s", appID, secretKey), + ), + ) + return fmt.Sprintf("Basic %s", base64Str) +} + +func GenSign(timeStamp uint64, accessId string, secretKey, requestBody string) string { + signBody := strconv.Itoa(int(timeStamp)) + accessId + requestBody + // Create a new HMAC by defining the hash type and the key (as byte array) + h := hmac.New(sha256.New, []byte(secretKey)) + // Write Data to it + h.Write([]byte(signBody)) + + // Get result and encode as hexadecimal string + sha := hex.EncodeToString(h.Sum(nil)) + //fmt.Println() + //fmt.Println("timeStamp: " + strconv.Itoa(int(timeStamp)) + " accessID:" + accessId + " body:" + requestBody) + sEnc := b64.StdEncoding.EncodeToString([]byte(sha)) + //fmt.Println("final Result " + sEnc) + return sEnc +} diff --git a/src/push/sdk/tpns-server-sdk-go/go/client/client.go b/src/push/sdk/tpns-server-sdk-go/go/client/client.go new file mode 100644 index 000000000..848724402 --- /dev/null +++ b/src/push/sdk/tpns-server-sdk-go/go/client/client.go @@ -0,0 +1,18 @@ +package client + +import ( + "net/http" + "time" +) + +func New() *http.Client { + return &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 30 * time.Second, + DisableCompression: false, + DisableKeepAlives: false, + }, + } +} diff --git a/src/push/sdk/tpns-server-sdk-go/go/common/http_helper.go b/src/push/sdk/tpns-server-sdk-go/go/common/http_helper.go new file mode 100644 index 000000000..31ef387ff --- /dev/null +++ b/src/push/sdk/tpns-server-sdk-go/go/common/http_helper.go @@ -0,0 +1,62 @@ +package common + +import ( + tpns "Open_IM/src/push/sdk/tpns-server-sdk-go/go" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +func PushAndGetResult(pushReq *http.Request) { + c := &http.Client{} + rsp, err := c.Do(pushReq) + fmt.Println() + if err != nil { + //fmt.Printf("http err:%v", err) + return + } + defer rsp.Body.Close() + body, err := ioutil.ReadAll(rsp.Body) + //fmt.Printf("http ReadAll err:%v, body:%v ", err, string(body)) + if err != nil { + return + } + r := &tpns.CommonRsp{} + json.Unmarshal(body, r) + //fmt.Printf("push result: %+v", r) +} + +func UploadFile(req *http.Request) (int, error) { + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return 0, err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return 0, err + } + + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("response error, status: %s, body: %s", resp.Status, string(body)) + } + + type uploadResponse struct { + RetCode int `json:"retCode"` + ErrMsg string `json:"errMsg"` + UploadId int `json:"uploadId"` + } + + var ur uploadResponse + if err := json.Unmarshal(body, &ur); err != nil { + return 0, err + } + + if ur.RetCode != 0 { + return 0, fmt.Errorf("response with %d:%s", ur.RetCode, ur.ErrMsg) + } + return ur.UploadId, nil +} diff --git a/src/push/sdk/tpns-server-sdk-go/go/common/json_helper.go b/src/push/sdk/tpns-server-sdk-go/go/common/json_helper.go new file mode 100644 index 000000000..368cfd843 --- /dev/null +++ b/src/push/sdk/tpns-server-sdk-go/go/common/json_helper.go @@ -0,0 +1,8 @@ +package common + +import "encoding/json" + +func ToJson(v interface{}) string { + bs, _ := json.Marshal(v) + return string(bs) +} diff --git a/src/push/sdk/tpns-server-sdk-go/go/def.go b/src/push/sdk/tpns-server-sdk-go/go/def.go new file mode 100644 index 000000000..3e5d383e4 --- /dev/null +++ b/src/push/sdk/tpns-server-sdk-go/go/def.go @@ -0,0 +1,256 @@ +package tpns + +type CommonRspEnv string + +const ( + // EnvProd + EnvProd CommonRspEnv = "product" + // EnvDev + EnvDev CommonRspEnv = "dev" +) + +type CommonRsp struct { + // TODO: doc this + Seq int64 `json:"seq"` + + PushID string `json:"push_id"` + + RetCode int `json:"ret_code"` + + Environment CommonRspEnv `json:"environment"` + + ErrMsg string `json:"err_msg,omitempty"` + + Result map[string]string `json:"result,omitempty"` +} + +type AudienceType string + +const ( + AdAll AudienceType = "all" + + AdTag AudienceType = "tag" + + AdToken AudienceType = "token" + + AdTokenList AudienceType = "token_list" + + AdAccount AudienceType = "account" + + AdAccountList AudienceType = "account_list" + + AdPackageAccount AudienceType = "package_account_push" + + AdPackageToken AudienceType = "package_token_push" +) + +// MessageType push API message_type +type MessageType string + +const ( + MsgTypeNotify MessageType = "notify" + + MsgTypeMessage MessageType = "message" +) + +type Request struct { + AudienceType AudienceType `json:"audience_type"` + + Message Message `json:"message"` + + MessageType MessageType `json:"message_type"` + + Tag []TagRule `json:"tag_rules,omitempty"` + + TokenList []string `json:"token_list,omitempty"` + + AccountList []string `json:"account_list,omitempty"` + + Environment CommonRspEnv `json:"environment,omitempty"` + + UploadId int `json:"upload_id,omitempty"` + + ExpireTime int `json:"expire_time,omitempty"` + + SendTime string `json:"send_time,omitempty"` + + MultiPkg bool `json:"multi_pkg,omitempty"` + + PlanId string `json:"plan_id,omitempty"` + + AccountPushType int `json:"account_push_type,omitempty"` + + PushSpeed int `json:"push_speed,omitempty"` + + CollapseId int `json:"collapse_id"` + + TPNSOnlinePushType int `json:"tpns_online_push_type"` + + ChannelRules []*ChannelDistributeRule `json:"channel_rules,omitempty"` + + LoopParam *PushLoopParam `json:"loop_param,omitempty"` + ForceCollapse bool `json:"force_collapse"` +} + +type TagListOperation string + +type ChannelDistributeRule struct { + ChannelName string `json:"channel"` + Disable bool `json:"disable"` +} + +type PushLoopParam struct { + StartDate string `json:"startDate"` + + EndDate string `json:"endDate"` + + LoopType PushLoopType `json:"loopType"` + + LoopDayIndexs []uint32 `json:"loopDayIndexs"` + + DayTimes []string `json:"dayTimes"` +} + +type PushLoopType int32 + +const ( + TagListOpAnd TagListOperation = "AND" + + TagListOpOr TagListOperation = "OR" +) + +type TagType string + +const ( + XGAutoProvince TagType = "xg_auto_province" + XGAutoActive TagType = "xg_auto_active" + XGUserDefine TagType = "xg_user_define" + XGAutoVersion TagType = "xg_auto_version" + XGAutoSdkversion TagType = "xg_auto_sdkversion" + XGAutoDevicebrand TagType = "xg_auto_devicebrand" + XGAutoDeviceversion TagType = "xg_auto_deviceversion" + XGAutoCountry TagType = "xg_auto_country" +) + +type TagRule struct { + TagItems []TagItem `json:"tag_items"` + + IsNot bool `json:"is_not"` + + Operator TagListOperation `json:"operator"` +} + +type TagItem struct { + // 标签 + Tags []string `json:"tags"` + IsNot bool `json:"is_not"` + TagsOperator TagListOperation `json:"tags_operator"` + ItemsOperator TagListOperation `json:"items_operator"` + TagType TagType `json:"tag_type"` +} + +type Message struct { + Title string `json:"title,omitempty"` + Content string `json:"content,omitempty"` + + AcceptTime []AcceptTimeItem `json:"accept_time,omitempty"` + + Android *AndroidParams `json:"android,omitempty"` + + IOS *IOSParams `json:"ios,omitempty"` + + ThreadId string `json:"thread_id,omitempty"` + + ThreadSumtext string `json:"thread_sumtext,omitempty"` + + XGMediaResources string `json:"xg_media_resources,omitempty"` + + XGMediaAudioResources string `json:"xg_media_audio_resources,omitempty"` +} + +type AcceptTimeItem struct { + Start HourAndMin `json:"start,omitempty"` + End HourAndMin `json:"end,omitempty"` +} + +type HourAndMin struct { + Hour string `json:"hour,omitempty"` + Min string `json:"min,omitempty"` +} + +type AndroidParams struct { + BuilderId *int `json:"builder_id,omitempty"` + + Ring *int `json:"ring,omitempty"` + + RingRaw string `json:"ring_raw,omitempty"` + + Vibrate *int `json:"vibrate,omitempty"` + + Lights *int `json:"lights,omitempty"` + + Clearable *int `json:"clearable,omitempty"` + + IconType *int `json:"icon_type"` + + IconRes string `json:"icon_res,omitempty"` + + StyleId *int `json:"style_id,omitempty"` + + SmallIcon string `json:"small_icon,omitempty"` + + Action *Action `json:"action,omitempty"` + + CustomContent string `json:"custom_content,omitempty"` + + ShowType *int `json:"show_type,omitempty"` + + NChId string `json:"n_ch_id,omitempty"` + + NChName string `json:"n_ch_name,omitempty"` + + HwChId string `json:"hw_ch_id,omitempty"` + + XmChId string `json:"xm_ch_id,omitempty"` + + OppoChId string `json:"oppo_ch_id,omitempty"` + + VivoChId string `json:"vivo_ch_id,omitempty"` + + BadgeType *int `json:"badge_type,omitempty"` + + IconColor *int `json:"icon_color,omitempty"` +} + +type Action struct { + ActionType *int `json:"action_type,omitempty"` + Activity string `json:"activity"` + AtyAttr AtyAttr `json:"aty_attr,omitempty"` + Intent string `json:"intent"` + Browser Browser `json:"browser,omitempty"` +} + +type Browser struct { + Url string `json:"url,omitempty"` + Confirm *int `json:"confirm,omitempty"` +} + +type AtyAttr struct { + AttrIf *int `json:"if,omitempty"` + Pf *int `json:"pf,omitempty"` +} + +type IOSParams struct { + Aps *Aps `json:"aps,omitempty"` + + CustomContent string `json:"custom_content,omitempty"` +} + +type Aps struct { + Alert map[string]string `json:"alert,omitempty"` + BadgeType *int `json:"badge_type,omitempty"` + Category string `json:"category,omitempty"` + ContentAvailableInt *int `json:"content-available,omitempty"` + MutableContent *int `json:"mutable-content,omitempty"` + Sound string `json:"sound,omitempty"` +} diff --git a/src/push/sdk/tpns-server-sdk-go/go/req/req.go b/src/push/sdk/tpns-server-sdk-go/go/req/req.go new file mode 100644 index 000000000..cc4195ec7 --- /dev/null +++ b/src/push/sdk/tpns-server-sdk-go/go/req/req.go @@ -0,0 +1,403 @@ +package req + +import ( + tpns "Open_IM/src/push/sdk/tpns-server-sdk-go/go" + "bytes" + "encoding/json" + "io" + "mime/multipart" + "net/http" + "os" + "path/filepath" +) + +var PushURL = "https://api.tpns.tencent.com/v3/push/app" + +//var PushURL = "https://test.api.tpns.tencent.com/v3/push/app" + +func URL(url string) { + PushURL = url +} + +type ReqOpt func(*tpns.Request) + +func NewPush(req *tpns.Request, opts ...ReqOpt) (*http.Request, string, error) { + return NewPushReq(req, opts...) +} + +func NewUploadFileRequest(host string, file string) (*http.Request, error) { + fp, err := os.Open(file) + if err != nil { + return nil, err + } + + defer fp.Close() + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", filepath.Base(fp.Name())) + if err != nil { + return nil, err + } + + io.Copy(part, fp) + writer.Close() + url := host + "/v3/push/package/upload" + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", writer.FormDataContentType()) + + return req, nil +} + +func NewSingleAccountPush( + message tpns.Message, + account string, + opts ...ReqOpt, +) (*http.Request, string, error) { + req := &tpns.Request{ + MessageType: tpns.MsgTypeNotify, + AudienceType: tpns.AdAccountList, + AccountList: []string{account}, + Message: message, + } + return NewPushReq(req, opts...) +} + +func NewListAccountPush( + accounts []string, message tpns.Message, + opts ...ReqOpt, +) (*http.Request, string, error) { + req := &tpns.Request{ + MessageType: tpns.MsgTypeNotify, + AudienceType: tpns.AdAccountList, + AccountList: accounts, + Message: message, + Environment: tpns.EnvDev, + } + return NewPushReq(req, opts...) +} + +func NewTokenPush( + tokens []string, message tpns.Message, + opts ...ReqOpt, +) (*http.Request, string, error) { + req := &tpns.Request{ + MessageType: tpns.MsgTypeNotify, + AudienceType: tpns.AdTokenList, + TokenList: tokens, + Message: message, + Environment: tpns.EnvProd, + } + //fmt.Printf("reqBody :%v", common.ToJson(req)) + //fmt.Println() + return NewPushReq(req, opts...) +} + +func NewTagsPush( + tagList []tpns.TagRule, message tpns.Message, + opts ...ReqOpt, +) (*http.Request, string, error) { + req := &tpns.Request{ + MessageType: tpns.MsgTypeNotify, + AudienceType: tpns.AdTag, + Tag: tagList, + Message: message, + } + //fmt.Printf("reqBody :%v", common.ToJson(req)) + //fmt.Println() + return NewPushReq(req, opts...) +} + +func NewAllPush( + message tpns.Message, + opts ...ReqOpt, +) (*http.Request, string, error) { + req := &tpns.Request{ + MessageType: tpns.MsgTypeNotify, + AudienceType: tpns.AdAll, + Message: message, + } + return NewPushReq(req, opts...) +} + +func NewAccountPackagePush( + message tpns.Message, + opts ...ReqOpt, +) (*http.Request, string, error) { + req := &tpns.Request{ + MessageType: tpns.MsgTypeNotify, + AudienceType: tpns.AdPackageAccount, + Message: message, + } + return NewPushReq(req, opts...) +} + +func NewTokenPackagePush( + message tpns.Message, + opts ...ReqOpt, +) (*http.Request, string, error) { + req := &tpns.Request{ + MessageType: tpns.MsgTypeNotify, + AudienceType: tpns.AdPackageToken, + Message: message, + } + return NewPushReq(req, opts...) +} + +func NewPushReq(req *tpns.Request, opts ...ReqOpt) (request *http.Request, reqBody string, err error) { + for _, opt := range opts { + opt(req) + } + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, "", err + } + reqBody = string(bodyBytes) + //fmt.Printf("NewPushReq req:%v", reqBody) + request, err = http.NewRequest("POST", PushURL, bytes.NewReader(bodyBytes)) + if err != nil { + return nil, "", err + } + request.Header.Add("Content-Type", "application/json") + return +} + +func EnvProd() ReqOpt { + return func(r *tpns.Request) { + r.Environment = tpns.EnvProd + } +} + +func EnvDev() ReqOpt { + return func(r *tpns.Request) { + r.Environment = tpns.EnvDev + } +} + +func Title(t string) ReqOpt { + return func(r *tpns.Request) { + r.Message.Title = t + if r.Message.IOS != nil { + if r.Message.IOS.Aps != nil { + r.Message.IOS.Aps.Alert["title"] = t + } else { + r.Message.IOS.Aps = &tpns.Aps{ + Alert: map[string]string{"title": t}, + } + } + } else { + r.Message.IOS = &tpns.IOSParams{ + Aps: &tpns.Aps{ + Alert: map[string]string{"title": t}, + }, + } + } + } +} + +func Content(c string) ReqOpt { + return func(r *tpns.Request) { + r.Message.Content = c + if r.Message.IOS != nil { + if r.Message.IOS.Aps != nil { + r.Message.IOS.Aps.Alert["body"] = c + } else { + r.Message.IOS.Aps = &tpns.Aps{ + Alert: map[string]string{"body": c}, + } + } + } else { + r.Message.IOS = &tpns.IOSParams{ + Aps: &tpns.Aps{ + Alert: map[string]string{"body": c}, + }, + } + } + } +} + +func Ring(ring *int) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.Ring = ring + } +} + +func RingRaw(rr string) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.RingRaw = rr + } +} + +func Vibrate(v *int) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.Vibrate = v + } +} + +func Lights(l *int) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.Lights = l + } +} + +func Clearable(c *int) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.Clearable = c + } +} + +func IconType(it *int) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.IconType = it + } +} + +func IconRes(ir string) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.IconRes = ir + } +} + +func AndroidCustomContent(ct string) ReqOpt { + return func(r *tpns.Request) { + r.Message.Android.CustomContent = ct + } +} + +func Aps(aps *tpns.Aps) ReqOpt { + return func(r *tpns.Request) { + r.Message.IOS.Aps = aps + } +} + +func AudienceType(at tpns.AudienceType) ReqOpt { + return func(r *tpns.Request) { + r.AudienceType = at + } +} + +func Message(m tpns.Message) ReqOpt { + return func(r *tpns.Request) { + r.Message = m + } +} + +func TokenList(tl []string) ReqOpt { + return func(r *tpns.Request) { + r.TokenList = tl + } +} + +func TokenListAdd(t string) ReqOpt { + return func(r *tpns.Request) { + if r.TokenList != nil { + r.TokenList = append(r.TokenList, t) + } else { + r.TokenList = []string{t} + } + } +} + +func AccountList(al []string) ReqOpt { + return func(r *tpns.Request) { + r.AccountList = al + } +} + +//ChannelDistributeRules +func AddChannelRules(ChannelRules []*tpns.ChannelDistributeRule) ReqOpt { + return func(r *tpns.Request) { + r.ChannelRules = ChannelRules + } +} + +//ChannelDistributeRules +func AddLoopParam(loopParam *tpns.PushLoopParam) ReqOpt { + return func(r *tpns.Request) { + r.LoopParam = loopParam + } +} + +func AccountListAdd(a string) ReqOpt { + return func(r *tpns.Request) { + if r.AccountList != nil { + r.AccountList = append(r.AccountList, a) + } else { + r.AccountList = []string{a} + } + } +} + +func MessageType(t tpns.MessageType) ReqOpt { + return func(r *tpns.Request) { + r.MessageType = t + } +} + +func AddMultiPkg(multipPkg bool) ReqOpt { + return func(r *tpns.Request) { + r.MultiPkg = multipPkg + } +} + +func AddForceCollapse(forceCollapse bool) ReqOpt { + return func(r *tpns.Request) { + r.ForceCollapse = forceCollapse + } +} + +func AddTPNSOnlinePushType(onlinePushType int) ReqOpt { + return func(r *tpns.Request) { + r.TPNSOnlinePushType = onlinePushType + } +} + +func AddCollapseId(collapseId int) ReqOpt { + return func(r *tpns.Request) { + r.CollapseId = collapseId + } +} + +func AddPushSpeed(pushSpeed int) ReqOpt { + return func(r *tpns.Request) { + r.PushSpeed = pushSpeed + } +} + +func AddAccountPushType(accountPushType int) ReqOpt { + return func(r *tpns.Request) { + r.AccountPushType = accountPushType + } +} + +func AddPlanId(planId string) ReqOpt { + return func(r *tpns.Request) { + r.PlanId = planId + } +} + +func AddSendTime(sendTime string) ReqOpt { + return func(r *tpns.Request) { + r.SendTime = sendTime + } +} + +func AddExpireTime(expireTime int) ReqOpt { + return func(r *tpns.Request) { + r.ExpireTime = expireTime + } +} + +func AddUploadId(UploadId int) ReqOpt { + return func(r *tpns.Request) { + r.UploadId = UploadId + } +} + +func AddEnvironment(Environment tpns.CommonRspEnv) ReqOpt { + return func(r *tpns.Request) { + r.Environment = Environment + } +}