diff --git a/cmd/open_im_api/main.go b/cmd/open_im_api/main.go index 63be3a496..2bfca024f 100644 --- a/cmd/open_im_api/main.go +++ b/cmd/open_im_api/main.go @@ -131,6 +131,7 @@ func main() { thirdGroup.POST("/get_download_url", apiThird.GetDownloadURL) thirdGroup.POST("/get_rtc_invitation_info", apiThird.GetRTCInvitationInfo) thirdGroup.POST("/get_rtc_invitation_start_app", apiThird.GetRTCInvitationInfoStartApp) + thirdGroup.POST("/fcm_update_token", apiThird.FcmUpdateToken) } //Message chatGroup := r.Group("/msg") diff --git a/config/config.yaml b/config/config.yaml index ccb952c04..eb7ad5045 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -217,6 +217,9 @@ push: appKey: "" intent: "" enable: false + fcm: #firebase cloud message 消息推送 + serviceAccount: "openim-*-firebase-adminsdk-*-*.json" #帐号文件,此处需要改修配置,并且这个文件放在 config目录下 + enable: false diff --git a/go.mod b/go.mod index 64bdb5476..ebe36aa77 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module Open_IM go 1.15 require ( + cloud.google.com/go/firestore v1.6.1 // indirect + firebase.google.com/go v3.13.0+incompatible github.com/Shopify/sarama v1.32.0 github.com/alibabacloud-go/darabonba-openapi v0.1.11 github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.8 @@ -52,7 +54,7 @@ require ( golang.org/x/net v0.0.0-20220622184535-263ec571b305 golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 // indirect golang.org/x/tools v0.1.11 // indirect - google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect + google.golang.org/api v0.59.0 google.golang.org/grpc v1.45.0 google.golang.org/protobuf v1.28.0 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/internal/api/third/fcm_update_token.go b/internal/api/third/fcm_update_token.go new file mode 100644 index 000000000..a5a3a9d42 --- /dev/null +++ b/internal/api/third/fcm_update_token.go @@ -0,0 +1,52 @@ +package apiThird + +import ( + "Open_IM/pkg/common/db" + "Open_IM/pkg/common/log" + "Open_IM/pkg/common/token_verify" + "Open_IM/pkg/utils" + "net/http" + + "github.com/gin-gonic/gin" +) + +/** + * FCM第三方上报Token + */ +type FcmUpdateTokenReq struct { + OperationID string `json:"operationID"` + Platform int `json:"platform" binding:"required,min=1,max=2"` //only for ios + android + FcmToken string `json:"fcmToken"` +} + +func FcmUpdateToken(c *gin.Context) { + var ( + req FcmUpdateTokenReq + ) + if err := c.Bind(&req); err != nil { + log.NewError("0", utils.GetSelfFuncName(), "BindJSON failed ", err.Error()) + c.JSON(http.StatusBadRequest, gin.H{"errCode": 400, "errMsg": err.Error()}) + return + } + log.NewInfo(req.OperationID, utils.GetSelfFuncName(), req) + + ok, UserId, errInfo := token_verify.GetUserIDFromToken(c.Request.Header.Get("token"), req.OperationID) + if !ok { + errMsg := req.OperationID + " " + "GetUserIDFromToken failed " + errInfo + " token:" + c.Request.Header.Get("token") + log.NewError(req.OperationID, errMsg) + c.JSON(http.StatusInternalServerError, gin.H{"errCode": 500, "errMsg": errMsg}) + return + } + log.NewInfo(req.OperationID, utils.GetSelfFuncName(), req, UserId) + //逻辑处理开始 + err := db.DB.SetFcmToken(UserId, int(req.Platform), req.FcmToken, 0) + if err != nil { + errMsg := req.OperationID + " " + "SetFcmToken failed " + err.Error() + " token:" + c.Request.Header.Get("token") + log.NewError(req.OperationID, errMsg) + c.JSON(http.StatusInternalServerError, gin.H{"errCode": 500, "errMsg": errMsg}) + return + } + //逻辑处理完毕 + c.JSON(http.StatusOK, gin.H{"errCode": 0, "errMsg": ""}) + return +} diff --git a/internal/push/fcm/push.go b/internal/push/fcm/push.go new file mode 100644 index 000000000..6fcfad70d --- /dev/null +++ b/internal/push/fcm/push.go @@ -0,0 +1,98 @@ +package push + +import ( + "Open_IM/internal/push" + "Open_IM/pkg/common/config" + "Open_IM/pkg/common/db" + "context" + "log" + "path/filepath" + "strconv" + + firebase "firebase.google.com/go" + "firebase.google.com/go/messaging" + "google.golang.org/api/option" +) + +type Fcm struct { +} + +var ( + FcmClient *Fcm + FcmMsgCli *messaging.Client +) + +func init() { + FcmClient = newFcmClient() +} + +func newFcmClient() *Fcm { + opt := option.WithCredentialsFile(filepath.Join(config.Root, "config", config.Config.Push.Fcm.ServiceAccount)) + fcmApp, err := firebase.NewApp(context.Background(), nil, opt) + if err != nil { + log.Println("error initializing app: %v\n", err) + return nil + } + //授权 + // fcmClient, err := fcmApp.Auth(context.Background()) + // if err != nil { + // log.Println("error getting Auth client: %v\n", err) + // return + // } + // log.Printf("%#v\r\n", fcmClient) + ctx := context.Background() + FcmMsgCli, err = fcmApp.Messaging(ctx) + if err != nil { + log.Fatalf("error getting Messaging client: %v\n", err) + return nil + } + log.Println(FcmMsgCli) + return &Fcm{} +} + +func (f *Fcm) Push(accounts []string, alert, detailContent, operationID string, opts push.PushOpts) (string, error) { + //需要一个客户端的Token + // accounts->registrationToken + Tokens := make([]string, 0) + for _, account := range accounts { + IosfcmToken, IosErr := db.DB.GetFcmToken(account, 1) + AndroidfcmToken, AndroidErr := db.DB.GetFcmToken(account, 2) + if IosErr == nil { + Tokens = append(Tokens, IosfcmToken) + } + if AndroidErr == nil { + Tokens = append(Tokens, AndroidfcmToken) + } + } + tokenlen := len(Tokens) + // 500组为一个推送,我们用400好了 + limit := 400 + pages := int((tokenlen-1)/limit + 1) + Success := 0 + Fail := 0 + for i := 0; i < pages; i++ { + Msg := new(messaging.MulticastMessage) + Msg.Notification.Body = detailContent + Msg.Notification.Title = alert + ctx := context.Background() + max := (i+1)*limit - 1 + if max >= tokenlen { + max = tokenlen-1 + } + Msg.Tokens = Tokens[i*limit : max] + //SendMulticast sends the given multicast message to all the FCM registration tokens specified. + //The tokens array in MulticastMessage may contain up to 500 tokens. + //SendMulticast uses the `SendAll()` function to send the given message to all the target recipients. + //The responses list obtained from the return value corresponds to the order of the input tokens. + //An error from SendMulticast indicates a total failure -- i.e. + //the message could not be sent to any of the recipients. + //Partial failures are indicated by a `BatchResponse` return value. + response, err := FcmMsgCli.SendMulticast(ctx, Msg) + if err != nil { + log.Fatalln(err) + } + Success = Success + response.SuccessCount + Fail = Fail + response.FailureCount + } + return strconv.Itoa(Success) + " Success," + strconv.Itoa(Fail) + " Fail", nil +} diff --git a/internal/push/logic/init.go b/internal/push/logic/init.go index c9e168940..0b540af6a 100644 --- a/internal/push/logic/init.go +++ b/internal/push/logic/init.go @@ -8,6 +8,7 @@ package logic import ( pusher "Open_IM/internal/push" + fcm "Open_IM/internal/push/fcm" "Open_IM/internal/push/getui" jpush "Open_IM/internal/push/jpush" "Open_IM/pkg/common/config" @@ -41,6 +42,10 @@ func init() { if config.Config.Push.Jpns.Enable { offlinePusher = jpush.JPushClient } + + if config.Config.Push.Fcm.Enable { + offlinePusher = fcm.FcmClient + } } func Run() { diff --git a/pkg/common/config/config.go b/pkg/common/config/config.go index fec3d61bd..87c7921cc 100644 --- a/pkg/common/config/config.go +++ b/pkg/common/config/config.go @@ -196,6 +196,10 @@ type config struct { Intent string `yaml:"intent"` MasterSecret string `yaml:"masterSecret"` } + Fcm struct { + ServiceAccount string `yaml:"serviceAccount"` + Enable bool `yaml:"enable"` + } } Manager struct { AppManagerUid []string `yaml:"appManagerUid"` diff --git a/pkg/common/db/RedisModel.go b/pkg/common/db/RedisModel.go index bd6d634bc..a40a0bbb5 100644 --- a/pkg/common/db/RedisModel.go +++ b/pkg/common/db/RedisModel.go @@ -31,9 +31,10 @@ const ( SignalCache = "SIGNAL_CACHE:" SignalListCache = "SIGNAL_LIST_CACHE:" GlobalMsgRecvOpt = "GLOBAL_MSG_RECV_OPT" + FcmToken = "FCM_TOKEN:" groupUserMinSeq = "GROUP_USER_MIN_SEQ:" groupMaxSeq = "GROUP_MAX_SEQ" -) + ) func (d *DataBases) JudgeAccountEXISTS(account string) (bool, error) { key := accountTempCode + account diff --git a/pkg/common/db/model.go b/pkg/common/db/model.go index 545997547..9041b8d89 100644 --- a/pkg/common/db/model.go +++ b/pkg/common/db/model.go @@ -38,6 +38,7 @@ type RedisClient struct { client *go_redis.Client cluster *go_redis.ClusterClient go_redis.UniversalClient + enableCluster bool } func key(dbAddress, dbName string) string { @@ -58,10 +59,10 @@ func init() { uri = config.Config.Mongo.DBUri } else { if config.Config.Mongo.DBPassword != "" && config.Config.Mongo.DBUserName != "" { - uri = fmt.Sprintf("mongodb://%s:%s@%s/%s?maxPoolSize=%d", config.Config.Mongo.DBUserName, config.Config.Mongo.DBPassword, config.Config.Mongo.DBAddress, + uri = fmt.Sprintf("mongodb://%s:%s@%s/%s?maxPoolSize=%d&authSource=admin", config.Config.Mongo.DBUserName, config.Config.Mongo.DBPassword, config.Config.Mongo.DBAddress, config.Config.Mongo.DBDatabase, config.Config.Mongo.DBMaxPoolSize) } else { - uri = fmt.Sprintf("mongodb://%s/%s/?maxPoolSize=%d", + uri = fmt.Sprintf("mongodb://%s/%s/?maxPoolSize=%d&authSource=admin", config.Config.Mongo.DBAddress, config.Config.Mongo.DBDatabase, config.Config.Mongo.DBMaxPoolSize) }