Selaa lähdekoodia

消息通知不同的title

luoyangwei 1 vuosi sitten
vanhempi
sitoutus
70c27efa7d

+ 4 - 0
etc/websocket.debug.yaml

@@ -1,5 +1,9 @@
 environment: debug
 
+i18n:
+    format_bundle_file: json
+    root_path: ./locales
+
 auth:
     # server_auth_secret: "a4176e95ad5d4d05b012b81dd5833b40"
     server_auth_secret: "992443c835c347d6a8b7d046d0261671"

+ 7 - 2
etc/websocket.release.yaml

@@ -1,5 +1,12 @@
 environment: release
 
+i18n:
+    format_bundle_file: json
+    root_path: ./locales
+
+auth:
+    server_auth_secret: "a4176e95ad5d4d05b012b81dd5833b40"
+    
 websocket:
     #首次消息超时
     first_read_wait: 360
@@ -18,8 +25,6 @@ websocket:
     write_buffer_size: 1024
     nats_url: "nats://10.29.40.221:4333"
 
-auth:
-    server_auth_secret: "a4176e95ad5d4d05b012b81dd5833b40"
 
 logger:
     filename: "/var/log/sikey/websocket.log" #  日志文件路径

+ 4 - 0
etc/websocket.test.yaml

@@ -1,5 +1,9 @@
 environment: test
 
+i18n:
+    format_bundle_file: json
+    root_path: ./locales
+
 auth:
     server_auth_secret: "992443c835c347d6a8b7d046d0261671"
 

+ 8 - 0
locales/en.json

@@ -9,5 +9,13 @@
     },
     "device": {
         "unbound": "Unbound device"
+    },
+    "fcm": {
+        "newMsg": { "title": "New news", "body": "click to view" },
+        "chatting": { "title": "Chat", "body": "click to view" },
+        "location": { "title": "Location change", "body": "click to view" },
+        "fence": { "title": "Electronic fence", "body": "click to view" },
+        "notification": { "title": "New Notification","body": "click to view" },
+        "videoCall": { "title": "Call notification", "body": "click to view" }
     }
 }

+ 8 - 0
locales/zh.json

@@ -9,5 +9,13 @@
     },
     "device": {
         "unbound": "未绑定设备"
+    },
+    "fcm": {
+        "newMsg": { "title": "新消息", "body": "点击查看" },
+        "chatting": { "title": "语聊消息", "body": "点击查看" },
+        "location": { "title": "位置变动", "body": "点击查看" },
+        "fence": { "title": "电子围栏", "body": "点击查看" },
+        "notification": { "title": "新通知", "body": "点击查看" },
+        "videoCall": { "title": "来电通知", "body": "点击查看" }
     }
 }

+ 32 - 0
middleware/lang.go

@@ -0,0 +1,32 @@
+package middleware
+
+import (
+	"github.com/gin-contrib/i18n"
+	"github.com/gin-gonic/gin"
+	"golang.org/x/text/language"
+)
+
+var translate = map[string]language.Tag{
+	"zh-CN": language.Chinese,
+	"zh-TW": language.TraditionalChinese,
+	"zh-HK": language.TraditionalChinese,
+	"zh-MO": language.TraditionalChinese,
+	"zh-SG": language.SimplifiedChinese,
+}
+
+func Language() i18n.GetLngHandler {
+	return func(ctx *gin.Context, defaultLng string) string {
+		localization := ctx.GetHeader("Localization")
+		if localization == "" {
+			localization = defaultLng
+		}
+
+		// Translate to golang.org/x/text/language
+		if local, ok := translate[localization]; ok {
+			localization = local.String()
+		}
+
+		ctx.Set("localization", localization)
+		return localization
+	}
+}

+ 2 - 0
server/client.go

@@ -12,6 +12,7 @@ import (
 	"github.com/google/uuid"
 	"github.com/gorilla/websocket"
 	"github.com/nats-io/nats.go"
+	"github.com/nicksnyder/go-i18n/v2/i18n"
 	"github.com/rotisserie/eris"
 	"go.uber.org/zap"
 	"sikey.com/websocket/models"
@@ -33,6 +34,7 @@ type Client struct {
 	isWatch       bool   // isWatch 是否是手表
 	isSimpleMsg   bool   // isSimpleMsg 是否是简单消息
 	localization  string // localization 国际码
+	localizer     *i18n.Localizer
 	firebaseToken string // firebaseToken FCM 推送的 token
 	loginToken    string // loginToken 登录 token
 

+ 50 - 9
server/fcm.go

@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"encoding/json"
 	"log"
 	"time"
 
@@ -48,19 +49,45 @@ func NewFirebaseMessageServer(repos *repositories.Repositories) *FirebaseMessage
 	if err != nil {
 		log.Fatalln(err)
 	}
+
 	firebaseMessageServer := &FirebaseMessageServer{app: app, repos: repos}
 	go firebaseMessageServer.queueRun(ctx)
 	return firebaseMessageServer
 }
 
 func (f *FirebaseMessageServer) Send(ctx context.Context, uid string, message Message) error {
+	// "newMsg": { "title": "News", "body": "click to view" },
+	// "chatting": { "title": "Chat Message", "body": "click to view" },
+	// "location": { "title": "Location Changed", "body": "click to view" },
+	// "fence": { "title": "Electronic Fence", "body": "click to view" },
+	// "notification": { "title": "New Notification","body": "click to view" },
+	// "videoCall": { "title": "Call Notification", "body": "click to view" }
+	var title string
+	var body = "click to view"
+	messageType := message.MessageType()
+	switch messageType {
+	case MessageTypeDownChating, MessageTypeUpChating:
+		title = "Chat Message"
+	case MessageTypeLocation:
+		title = "Location Changed"
+	case MessageTypeNotification:
+		// var notification Notification
+		// _ = json.Unmarshal(message.Data(), &notification)
+		// switch notification.Content.ID {
+		// }
+		title = "New Notification"
+	case MessageTypeVideoCall:
+		title = "Call Notification"
+	default:
+		title = "News"
+	}
+
 	// 加入消息到 fcm 队列
 	queue := &models.FirebaseMessagingQueue{
-		Title:    "New message",
-		Body:     "received a new message",
-		Receiver: uid,
-		Data:     string(serializeMessage(message)),
-		// Status:           DefaultQueueStatus,
+		Title:            title,
+		Body:             body,
+		Receiver:         uid,
+		Data:             string(serializeMessage(message)),
 		RemainingRetries: DefaultRemainingRetries,
 	}
 	if err := f.repos.FirebaseMessageQueueRepository.Create(ctx, queue); err != nil {
@@ -129,13 +156,23 @@ func (f *FirebaseMessageServer) queueRun(ctx context.Context) {
 						return
 					}
 
+					aps := map[string]interface{}{
+						"alert": map[string]interface{}{
+							"title": queue.Title,
+							"body":  queue.Body,
+						},
+					}
+					apsBuf, _ := json.Marshal(aps)
 					msg := &messaging.Message{
 						Token: firebaseToken.Token,
-						Data:  map[string]string{"message": queue.Data},
-						Notification: &messaging.Notification{
-							Title: queue.Title,
-							Body:  queue.Body,
+						Data: map[string]string{
+							"aps":     string(apsBuf),
+							"message": queue.Data,
 						},
+						// Notification: &messaging.Notification{
+						// 	Title: queue.Title,
+						// 	Body:  queue.Body,
+						// },
 					}
 
 					zap.L().Info("[firebase] Send", zap.Any("msg", msg), zap.String("user_id", receiver))
@@ -144,6 +181,10 @@ func (f *FirebaseMessageServer) queueRun(ctx context.Context) {
 						zap.L().Error("[firebase] 发送错误", zap.Error(err), zap.String("user_id", receiver))
 						return
 					}
+
+					zap.L().Info("[firebase] Ack", zap.String("user_id", receiver))
+					client.Send(ctx, &messaging.Message{})
+
 					zap.L().Info("[firebase] Successfully",
 						zap.Any("msg", msg),
 						zap.String("user_id", receiver),

+ 72 - 0
server/fcm_test.go

@@ -0,0 +1,72 @@
+package server
+
+import (
+	"context"
+	"log"
+	"testing"
+
+	firebase "firebase.google.com/go"
+	"firebase.google.com/go/messaging"
+	"google.golang.org/api/option"
+)
+
+func TestFirebaseMessageServer_Send(t *testing.T) {
+	ctx := context.Background()
+	conf := &firebase.Config{ServiceAccountID: firebaseServiceAccountID}
+	app, err := firebase.NewApp(ctx, conf,
+		// option.WithHTTPClient(&http.Client{Timeout: 5 * time.Second}),
+		option.WithCredentialsJSON([]byte(firebaseCredentials)))
+	if err != nil {
+		log.Fatalln(err)
+	}
+
+	token := "foU7QZ1toU9zkP-QN_6Qlz:APA91bGNJ9FVZQAVXzrWAyVHdRqgOO9B15dNJqGY7xuhzefASlQp8itrpa4qfbEX4aYRLiRVx8YRu35Mj0CFetkdSBrZk2qz8gv6MHeoMKI0ivFdGGjS76wKFy4m24CDNS9bswUrsoyH"
+	clt, err := app.Messaging(ctx)
+	if err != nil {
+		panic(err)
+	}
+	// 	data := fmt.Sprintf(`
+	// 	message: {
+	// 		notification: {
+	// 			"title": "XXX",
+	// 			"body": "XXX"
+	// 		},
+	// 		data: { "key": "value" },
+	// 		token: %s,
+	// 		android: {
+	// 			"priority": "high"
+	// 		},
+	// 		apns: {
+	// 			"payload": {
+	// 				"aps": {
+	// 					"content-available": 1
+	// 				}
+	// 			},
+	// 		}
+	// 	}
+	//  `, token)
+	// aps := map[string]interface{}{
+	// 	"alert": map[string]interface{}{
+	// 		"title": "title",
+	// 		"body":  "body",
+	// 	},
+	// }
+	// apsBuf, _ := json.Marshal(aps)
+	msg := &messaging.Message{
+		Token: token,
+		Data: map[string]string{
+			"message": "hello word",
+		},
+		Notification: &messaging.Notification{
+			// Title: queue.Title,
+			// Body:  queue.Body,
+		},
+	}
+
+	r, err := clt.Send(ctx, msg)
+	if err != nil {
+		log.Fatalln(err)
+	} else {
+		log.Println(r)
+	}
+}

+ 8 - 1
server/server.go

@@ -11,6 +11,7 @@ import (
 	"github.com/golang-jwt/jwt/v5"
 	"github.com/google/uuid"
 	"github.com/gorilla/websocket"
+	"github.com/nicksnyder/go-i18n/v2/i18n"
 	"github.com/rotisserie/eris"
 	"github.com/spf13/viper"
 	"go.uber.org/zap"
@@ -89,7 +90,7 @@ func (srv *Server) events() {
 	}
 }
 
-func (srv *Server) WebsocketHandler(ctx *gin.Context) {
+func (srv *Server) WebsocketHandler(ctx *gin.Context, bundle *i18n.Bundle) {
 	// Builder headers
 	headers := headerBuilder(ctx)
 
@@ -135,6 +136,7 @@ func (srv *Server) WebsocketHandler(ctx *gin.Context) {
 		nats:           srv.nats,
 		UnderlyingConn: conn,
 		isWatch:        isWatch,
+		localizer:      i18n.NewLocalizer(bundle, headers[keys.LocalizationHeader].(string)),
 		firebaseToken:  firebaseMessageToken.Token,
 		loginToken:     headers[keys.AccessTokenHeader].(string),
 		online:         &models.Online{UserId: id, ServerId: srv.id},
@@ -214,6 +216,11 @@ func headerBuilder(ctx *gin.Context) Headers {
 	accessToken := request.URL.Query().Get(keys.AccessTokenHeader)
 	simple := request.URL.Query().Get(keys.SimpleHeader)
 	localization := request.URL.Query().Get(keys.LocalizationHeader)
+
+	if localization == "" {
+		localization = "en"
+	}
+
 	headers[keys.UserIdHeader] = request.URL.Query().Get(keys.UserIdHeader)
 	headers[keys.AccessTokenHeader] = accessToken
 	headers[keys.SimpleHeader] = simple == "1"

+ 33 - 1
websocket.go

@@ -1,6 +1,8 @@
 package main
 
 import (
+	"embed"
+	"encoding/json"
 	"flag"
 	"fmt"
 	"log"
@@ -12,6 +14,9 @@ import (
 	"github.com/denisbrodbeck/machineid"
 	"github.com/gin-gonic/gin"
 	"github.com/google/uuid"
+	"github.com/nicksnyder/go-i18n/v2/i18n"
+	"github.com/spf13/viper"
+	"golang.org/x/text/language"
 	"sikey.com/websocket/config"
 	"sikey.com/websocket/pkg/gid"
 	"sikey.com/websocket/server"
@@ -37,13 +42,40 @@ func main() {
 	}
 }
 
+//go:embed locales/*.json
+var LocaleFS embed.FS
+
 func newApp() *gin.Engine {
 	app := gin.Default()
 	ginpprof.Wrap(app)
 	gin.SetMode(gin.ReleaseMode)
 
+	// bundle := &i18n.BundleCfg{
+	// 	FormatBundleFile: viper.GetString("i18n.format_bundle_file"),
+	// 	RootPath:         viper.GetString("i18n.root_path"),
+	// 	UnmarshalFunc:    json.Unmarshal,
+	// 	AcceptLanguage:   []language.Tag{language.Chinese, language.English},
+	// 	DefaultLanguage:  language.English,
+	// }
+
+	// var localizeMiddleware = i18n.Localize(
+	// 	i18n.WithGetLngHandle(middleware.Language()),
+	// 	i18n.WithBundle(bundle),
+	// )
+	// app.Use(localizeMiddleware)
+
 	srv := server.NewServer()
-	app.GET("/websocket/endpoint", func(ctx *gin.Context) { srv.WebsocketHandler(ctx) })
+	app.GET("/websocket/endpoint", func(ctx *gin.Context) {
+
+		// i18n
+		rootPath := viper.GetString("i18n.root_path")
+		bundle := i18n.NewBundle(language.English)
+		bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
+		bundle.LoadMessageFile(rootPath + "/en.json")
+		bundle.LoadMessageFile(rootPath + "/zh.json")
+		// ctx.Set("bundle", bundle)
+		srv.WebsocketHandler(ctx, bundle)
+	})
 	app.GET("/websocket/clients", func(ctx *gin.Context) {
 		clients := srv.GetClients()
 		ctx.JSON(http.StatusOK, gin.H{"clients": clients})