Compare commits
No commits in common. "747d4700df95833f2ee91ad89eeef163ddf7df01" and "a02e22ac748af1d6529db823807e904e2e9f0ffe" have entirely different histories.
747d4700df
...
a02e22ac74
@ -15,7 +15,6 @@ import (
|
|||||||
"chatapp3-golang/internal/service/apppopup"
|
"chatapp3-golang/internal/service/apppopup"
|
||||||
"chatapp3-golang/internal/service/baishun"
|
"chatapp3-golang/internal/service/baishun"
|
||||||
"chatapp3-golang/internal/service/binancerecharge"
|
"chatapp3-golang/internal/service/binancerecharge"
|
||||||
"chatapp3-golang/internal/service/cprelationbroadcast"
|
|
||||||
"chatapp3-golang/internal/service/errorlog"
|
"chatapp3-golang/internal/service/errorlog"
|
||||||
"chatapp3-golang/internal/service/firstrechargereward"
|
"chatapp3-golang/internal/service/firstrechargereward"
|
||||||
"chatapp3-golang/internal/service/gameopen"
|
"chatapp3-golang/internal/service/gameopen"
|
||||||
@ -70,9 +69,6 @@ func main() {
|
|||||||
wheelService := wheel.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
wheelService := wheel.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
smashEggService := smashegg.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
smashEggService := smashegg.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
regionIMGroupService := regionimgroup.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
regionIMGroupService := regionimgroup.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
baishunService.SetHighWinBroadcaster(regionIMGroupService)
|
|
||||||
luckyGiftService.SetHighWinBroadcaster(regionIMGroupService)
|
|
||||||
cpRelationBroadcastService := cprelationbroadcast.NewService(app.Config, app.Repository.DB, regionIMGroupService)
|
|
||||||
voiceRoomRedPacketService := voiceroomredpacket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet, regionIMGroupService, app.Gateways.Room)
|
voiceRoomRedPacketService := voiceroomredpacket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet, regionIMGroupService, app.Gateways.Room)
|
||||||
voiceRoomRedPacketService.SetRegionResolver(&app.Gateways)
|
voiceRoomRedPacketService.SetRegionResolver(&app.Gateways)
|
||||||
voiceRoomRocketService := voiceroomrocket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
voiceRoomRocketService := voiceroomrocket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
||||||
@ -101,9 +97,6 @@ func main() {
|
|||||||
if err := firstRechargeRewardService.StartMessageConsumer(workerCtx); err != nil {
|
if err := firstRechargeRewardService.StartMessageConsumer(workerCtx); err != nil {
|
||||||
log.Fatalf("start first recharge reward message consumer failed: %v", err)
|
log.Fatalf("start first recharge reward message consumer failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := cpRelationBroadcastService.StartMessageConsumer(workerCtx); err != nil {
|
|
||||||
log.Fatalf("start cp relation broadcast message consumer failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := voiceRoomRocketService.Start(workerCtx); err != nil {
|
if err := voiceRoomRocketService.Start(workerCtx); err != nil {
|
||||||
log.Fatalf("start voice room rocket workers failed: %v", err)
|
log.Fatalf("start voice room rocket workers failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"chatapp3-golang/internal/bootstrap"
|
"chatapp3-golang/internal/bootstrap"
|
||||||
"chatapp3-golang/internal/service/cprelationbroadcast"
|
|
||||||
"chatapp3-golang/internal/service/firstrechargereward"
|
"chatapp3-golang/internal/service/firstrechargereward"
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
"chatapp3-golang/internal/service/regionimgroup"
|
||||||
"chatapp3-golang/internal/service/registerreward"
|
"chatapp3-golang/internal/service/registerreward"
|
||||||
@ -31,7 +30,6 @@ func main() {
|
|||||||
taskCenterService := taskcenter.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
taskCenterService := taskcenter.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
firstRechargeRewardService := firstrechargereward.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
firstRechargeRewardService := firstrechargereward.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
regionIMGroupService := regionimgroup.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
regionIMGroupService := regionimgroup.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
cpRelationBroadcastService := cprelationbroadcast.NewService(app.Config, app.Repository.DB, regionIMGroupService)
|
|
||||||
voiceRoomRedPacketService := voiceroomredpacket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet, regionIMGroupService, app.Gateways.Room)
|
voiceRoomRedPacketService := voiceroomredpacket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet, regionIMGroupService, app.Gateways.Room)
|
||||||
voiceRoomRedPacketService.SetRegionResolver(&app.Gateways)
|
voiceRoomRedPacketService.SetRegionResolver(&app.Gateways)
|
||||||
voiceRoomRocketService := voiceroomrocket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
voiceRoomRocketService := voiceroomrocket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
||||||
@ -52,9 +50,6 @@ func main() {
|
|||||||
if err := firstRechargeRewardService.StartMessageConsumer(workerCtx); err != nil {
|
if err := firstRechargeRewardService.StartMessageConsumer(workerCtx); err != nil {
|
||||||
log.Fatalf("start first recharge reward message consumer failed: %v", err)
|
log.Fatalf("start first recharge reward message consumer failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := cpRelationBroadcastService.StartMessageConsumer(workerCtx); err != nil {
|
|
||||||
log.Fatalf("start cp relation broadcast message consumer failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := voiceRoomRocketService.Start(workerCtx); err != nil {
|
if err := voiceRoomRocketService.Start(workerCtx); err != nil {
|
||||||
log.Fatalf("start voice room rocket workers failed: %v", err)
|
log.Fatalf("start voice room rocket workers failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ type Config struct {
|
|||||||
RoomTurnoverReward RoomTurnoverRewardConfig
|
RoomTurnoverReward RoomTurnoverRewardConfig
|
||||||
VoiceRoomRedPacket VoiceRoomRedPacketConfig
|
VoiceRoomRedPacket VoiceRoomRedPacketConfig
|
||||||
VoiceRoomRocket VoiceRoomRocketConfig
|
VoiceRoomRocket VoiceRoomRocketConfig
|
||||||
CPRelationBroadcast CPRelationBroadcastConfig
|
|
||||||
TencentIM TencentIMConfig
|
TencentIM TencentIMConfig
|
||||||
Baishun BaishunConfig
|
Baishun BaishunConfig
|
||||||
Lingxian LingxianConfig
|
Lingxian LingxianConfig
|
||||||
@ -213,25 +212,6 @@ type VoiceRoomRocketMQConfig struct {
|
|||||||
Tag string
|
Tag string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPRelationBroadcastConfig 保存 CP/兄弟/姐妹关系区域飘屏配置。
|
|
||||||
type CPRelationBroadcastConfig struct {
|
|
||||||
DefaultSysOrigin string
|
|
||||||
MQ CPRelationBroadcastMQConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// CPRelationBroadcastMQConfig 保存 CP 关系成功 MQ 消费配置。
|
|
||||||
type CPRelationBroadcastMQConfig struct {
|
|
||||||
Enabled bool
|
|
||||||
Endpoint string
|
|
||||||
Namespace string
|
|
||||||
AccessKey string
|
|
||||||
AccessSecret string
|
|
||||||
SecurityToken string
|
|
||||||
ConsumerGroup string
|
|
||||||
Topic string
|
|
||||||
Tag string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TencentIMConfig 保存腾讯 IM REST API 配置。
|
// TencentIMConfig 保存腾讯 IM REST API 配置。
|
||||||
type TencentIMConfig struct {
|
type TencentIMConfig struct {
|
||||||
AppID int64
|
AppID int64
|
||||||
@ -311,9 +291,6 @@ func Load() Config {
|
|||||||
voiceRoomRocketMQEndpoint := getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_ENDPOINT", "LIKEI_ROCKETMQ_ENDPOINT"}, "")
|
voiceRoomRocketMQEndpoint := getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_ENDPOINT", "LIKEI_ROCKETMQ_ENDPOINT"}, "")
|
||||||
voiceRoomRocketMQAccessKey := getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_ACCESS_KEY", "LIKEI_ROCKETMQ_ACCESS_KEY"}, "")
|
voiceRoomRocketMQAccessKey := getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_ACCESS_KEY", "LIKEI_ROCKETMQ_ACCESS_KEY"}, "")
|
||||||
voiceRoomRocketMQAccessSecret := getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_ACCESS_SECRET", "LIKEI_ROCKETMQ_SECRET_KEY"}, "")
|
voiceRoomRocketMQAccessSecret := getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_ACCESS_SECRET", "LIKEI_ROCKETMQ_SECRET_KEY"}, "")
|
||||||
cpRelationBroadcastMQEndpoint := getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_ENDPOINT", "CP_RELATION_BROADCAST_MQ_ENDPOINT", "LIKEI_ROCKETMQ_ENDPOINT"}, "")
|
|
||||||
cpRelationBroadcastMQAccessKey := getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_ACCESS_KEY", "CP_RELATION_BROADCAST_MQ_ACCESS_KEY", "LIKEI_ROCKETMQ_ACCESS_KEY"}, "")
|
|
||||||
cpRelationBroadcastMQAccessSecret := getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_ACCESS_SECRET", "CP_RELATION_BROADCAST_MQ_SECRET_KEY", "LIKEI_ROCKETMQ_SECRET_KEY"}, "")
|
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
HTTP: HTTPConfig{
|
HTTP: HTTPConfig{
|
||||||
@ -549,23 +526,6 @@ func Load() Config {
|
|||||||
Tag: getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_TAG", "VOICE_ROOM_ROCKET_MQ_TAG"}, "give_gift_v3"),
|
Tag: getEnvAny([]string{"CHATAPP_VOICE_ROOM_ROCKET_MQ_TAG", "VOICE_ROOM_ROCKET_MQ_TAG"}, "give_gift_v3"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CPRelationBroadcast: CPRelationBroadcastConfig{
|
|
||||||
DefaultSysOrigin: strings.ToUpper(getEnvAny(
|
|
||||||
[]string{"CHATAPP_CP_RELATION_BROADCAST_DEFAULT_SYS_ORIGIN", "CP_RELATION_BROADCAST_DEFAULT_SYS_ORIGIN", "CHATAPP_WEEK_STAR_DEFAULT_SYS_ORIGIN", "WEEK_STAR_DEFAULT_SYS_ORIGIN"},
|
|
||||||
"LIKEI",
|
|
||||||
)),
|
|
||||||
MQ: CPRelationBroadcastMQConfig{
|
|
||||||
Enabled: getEnvBoolAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_ENABLED", "CP_RELATION_BROADCAST_MQ_ENABLED"}, false),
|
|
||||||
Endpoint: cpRelationBroadcastMQEndpoint,
|
|
||||||
Namespace: getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_NAMESPACE", "CP_RELATION_BROADCAST_MQ_NAMESPACE"}, ""),
|
|
||||||
AccessKey: cpRelationBroadcastMQAccessKey,
|
|
||||||
AccessSecret: cpRelationBroadcastMQAccessSecret,
|
|
||||||
SecurityToken: getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_SECURITY_TOKEN", "CP_RELATION_BROADCAST_MQ_SECURITY_TOKEN"}, ""),
|
|
||||||
ConsumerGroup: getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_CONSUMER_GROUP", "CP_RELATION_BROADCAST_MQ_CONSUMER_GROUP"}, "cp-relation-broadcast"),
|
|
||||||
Topic: getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_TOPIC", "CP_RELATION_BROADCAST_MQ_TOPIC"}, "RC_DEFAULT_APP_ORDINARY"),
|
|
||||||
Tag: getEnvAny([]string{"CHATAPP_CP_RELATION_BROADCAST_MQ_TAG", "CP_RELATION_BROADCAST_MQ_TAG"}, "cp_relation_broadcast"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TencentIM: TencentIMConfig{
|
TencentIM: TencentIMConfig{
|
||||||
AppID: getEnvInt64Any(
|
AppID: getEnvInt64Any(
|
||||||
[]string{"CHATAPP_TENCENT_IM_APP_ID", "TENCENT_IM_APP_ID", "LIKEI_IM_APP_ID"},
|
[]string{"CHATAPP_TENCENT_IM_APP_ID", "TENCENT_IM_APP_ID", "LIKEI_IM_APP_ID"},
|
||||||
|
|||||||
@ -102,27 +102,6 @@ func TestLoadTaskCenterMQUsesSharedRocketMQCredentials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadCPRelationBroadcastMQUsesSharedRocketMQCredentials(t *testing.T) {
|
|
||||||
t.Setenv("CHATAPP_CP_RELATION_BROADCAST_MQ_ENABLED", "true")
|
|
||||||
t.Setenv("LIKEI_ROCKETMQ_ENDPOINT", "rmq.example.com:8081")
|
|
||||||
t.Setenv("LIKEI_ROCKETMQ_ACCESS_KEY", "access-key")
|
|
||||||
t.Setenv("LIKEI_ROCKETMQ_SECRET_KEY", "access-secret")
|
|
||||||
|
|
||||||
cfg := Load()
|
|
||||||
if !cfg.CPRelationBroadcast.MQ.Enabled {
|
|
||||||
t.Fatalf("expected cp relation broadcast mq enabled")
|
|
||||||
}
|
|
||||||
if got := cfg.CPRelationBroadcast.MQ.Endpoint; got != "rmq.example.com:8081" {
|
|
||||||
t.Fatalf("cp relation broadcast mq endpoint = %q", got)
|
|
||||||
}
|
|
||||||
if got := cfg.CPRelationBroadcast.MQ.Topic; got != "RC_DEFAULT_APP_ORDINARY" {
|
|
||||||
t.Fatalf("cp relation broadcast mq topic = %q", got)
|
|
||||||
}
|
|
||||||
if got := cfg.CPRelationBroadcast.MQ.Tag; got != "cp_relation_broadcast" {
|
|
||||||
t.Fatalf("cp relation broadcast mq tag = %q", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadTaskCenterArchiveUsesSharedCOSCredentials(t *testing.T) {
|
func TestLoadTaskCenterArchiveUsesSharedCOSCredentials(t *testing.T) {
|
||||||
t.Setenv("CHATAPP_TASK_CENTER_ARCHIVE_ENABLED", "true")
|
t.Setenv("CHATAPP_TASK_CENTER_ARCHIVE_ENABLED", "true")
|
||||||
t.Setenv("LIKEI_TENCENT_COS_BUCKET", "bucket-123")
|
t.Setenv("LIKEI_TENCENT_COS_BUCKET", "bucket-123")
|
||||||
|
|||||||
@ -248,7 +248,7 @@ func registerBaishunRoutes(
|
|||||||
})
|
})
|
||||||
callbackGroup.POST("/report", func(c *gin.Context) {
|
callbackGroup.POST("/report", func(c *gin.Context) {
|
||||||
raw, payload := readRawPayload(c)
|
raw, payload := readRawPayload(c)
|
||||||
writeBaishunJSON(c, http.StatusOK, baishunService.HandleReport(c.Request.Context(), mapToReportRequest(payload), raw))
|
writeBaishunJSON(c, http.StatusOK, baishunService.HandleReport(c.Request.Context(), payload, raw))
|
||||||
})
|
})
|
||||||
callbackGroup.POST("/balance-info", func(c *gin.Context) {
|
callbackGroup.POST("/balance-info", func(c *gin.Context) {
|
||||||
raw, payload := readRawPayload(c)
|
raw, payload := readRawPayload(c)
|
||||||
@ -338,16 +338,6 @@ func mapToChangeBalanceRequest(payload map[string]any) baishun.BaishunChangeBala
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapToReportRequest 把百顺 report 回调报文映射成服务层 DTO。
|
|
||||||
func mapToReportRequest(payload map[string]any) baishun.BaishunReportRequest {
|
|
||||||
return baishun.BaishunReportRequest{
|
|
||||||
ReportType: asString(payload["report_type"]),
|
|
||||||
ReportMsg: asMap(payload["report_msg"]),
|
|
||||||
UserID: asString(payload["user_id"]),
|
|
||||||
SSToken: asString(payload["ss_token"]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapToBalanceInfoRequest 把百顺 balance-info 回调报文映射成服务层 DTO。
|
// mapToBalanceInfoRequest 把百顺 balance-info 回调报文映射成服务层 DTO。
|
||||||
func mapToBalanceInfoRequest(payload map[string]any) baishun.BaishunBalanceInfoRequest {
|
func mapToBalanceInfoRequest(payload map[string]any) baishun.BaishunBalanceInfoRequest {
|
||||||
return baishun.BaishunBalanceInfoRequest{
|
return baishun.BaishunBalanceInfoRequest{
|
||||||
@ -360,26 +350,6 @@ func mapToBalanceInfoRequest(payload map[string]any) baishun.BaishunBalanceInfoR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// asMap 宽松读取对象字段。
|
|
||||||
func asMap(value any) map[string]any {
|
|
||||||
switch typed := value.(type) {
|
|
||||||
case map[string]any:
|
|
||||||
return typed
|
|
||||||
case string:
|
|
||||||
result := map[string]any{}
|
|
||||||
_ = json.Unmarshal([]byte(typed), &result)
|
|
||||||
return result
|
|
||||||
default:
|
|
||||||
body, err := json.Marshal(typed)
|
|
||||||
if err != nil {
|
|
||||||
return map[string]any{}
|
|
||||||
}
|
|
||||||
result := map[string]any{}
|
|
||||||
_ = json.Unmarshal(body, &result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// asString 宽松读取字符串字段。
|
// asString 宽松读取字符串字段。
|
||||||
func asString(value any) string {
|
func asString(value any) string {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
|
|||||||
@ -60,29 +60,6 @@ func registerManagerCenterRoutes(engine *gin.Engine, javaClient authGateway, ser
|
|||||||
writeOK(c, resp)
|
writeOK(c, resp)
|
||||||
})
|
})
|
||||||
|
|
||||||
group.GET("/bd-leaders", func(c *gin.Context) {
|
|
||||||
resp, err := service.ListBDLeaders(c.Request.Context(), mustAuthUser(c))
|
|
||||||
if err != nil {
|
|
||||||
writeError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeOK(c, resp)
|
|
||||||
})
|
|
||||||
|
|
||||||
group.POST("/bd-leaders", func(c *gin.Context) {
|
|
||||||
var req managercenter.AddBDLeaderRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
writeError(c, managercenter.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp, err := service.AddBDLeader(c.Request.Context(), mustAuthUser(c), req)
|
|
||||||
if err != nil {
|
|
||||||
writeError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeOK(c, resp)
|
|
||||||
})
|
|
||||||
|
|
||||||
group.POST("/users/ban", func(c *gin.Context) {
|
group.POST("/users/ban", func(c *gin.Context) {
|
||||||
var req managercenter.BanUserRequest
|
var req managercenter.BanUserRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
|||||||
@ -243,6 +243,21 @@ func (s *BaishunService) HandleChangeBalance(ctx context.Context, req BaishunCha
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleReport 处理百顺上报类回调,目前仅记录日志并返回成功。
|
||||||
|
func (s *BaishunService) HandleReport(ctx context.Context, payload map[string]any, rawJSON string) baishunStandardResponse {
|
||||||
|
userID := toString(payload["user_id"])
|
||||||
|
orderID := toString(payload["order_id"])
|
||||||
|
gameRoundID := toString(payload["game_round_id"])
|
||||||
|
resp := baishunStandardResponse{
|
||||||
|
Code: 0,
|
||||||
|
Message: "succeed",
|
||||||
|
UniqueID: s.uniqueID(),
|
||||||
|
Data: map[string]any{},
|
||||||
|
}
|
||||||
|
s.saveCallbackLog(ctx, "report", rawJSON, utils.MustJSONString(resp, ""), "", userID, stringPtr(orderID), stringPtr(gameRoundID), baishunCallbackStatusSuccess, 0, resp.Message)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
func (s *BaishunService) reportGameConsumeTaskEvent(ctx context.Context, session model.BaishunLaunchSession, req BaishunChangeBalanceRequest) {
|
func (s *BaishunService) reportGameConsumeTaskEvent(ctx context.Context, session model.BaishunLaunchSession, req BaishunChangeBalanceRequest) {
|
||||||
if s.taskReporter == nil || req.CurrencyDiff >= 0 {
|
if s.taskReporter == nil || req.CurrencyDiff >= 0 {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -49,7 +49,6 @@ func newTestBaishunService(t *testing.T) (*BaishunService, *gorm.DB) {
|
|||||||
&model.BaishunProviderConfig{},
|
&model.BaishunProviderConfig{},
|
||||||
&model.BaishunGameCatalog{},
|
&model.BaishunGameCatalog{},
|
||||||
&model.BaishunLaunchSession{},
|
&model.BaishunLaunchSession{},
|
||||||
&model.BaishunCallbackLog{},
|
|
||||||
&model.BaishunRoomState{},
|
&model.BaishunRoomState{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("auto migrate: %v", err)
|
t.Fatalf("auto migrate: %v", err)
|
||||||
|
|||||||
@ -1,291 +0,0 @@
|
|||||||
package baishun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
"chatapp3-golang/internal/service/highwin"
|
|
||||||
"chatapp3-golang/internal/utils"
|
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
baishunReportTypeStart = "game_start"
|
|
||||||
baishunReportTypeSettle = "game_settle"
|
|
||||||
baishunReportBetTTL = 24 * time.Hour
|
|
||||||
baishunReportBetRedisPrefix = "baishun:report:bet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type baishunGameBetRecord struct {
|
|
||||||
SysOrigin string `json:"sysOrigin"`
|
|
||||||
RoomID string `json:"roomId"`
|
|
||||||
GameID int64 `json:"gameId"`
|
|
||||||
GameRoundID string `json:"gameRoundId"`
|
|
||||||
UserID int64 `json:"userId"`
|
|
||||||
Bet int64 `json:"bet"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaishunService) HandleReport(ctx context.Context, req BaishunReportRequest, rawJSON string) baishunStandardResponse {
|
|
||||||
resp := baishunStandardResponse{
|
|
||||||
Code: 0,
|
|
||||||
Message: "succeed",
|
|
||||||
UniqueID: s.uniqueID(),
|
|
||||||
Data: map[string]any{},
|
|
||||||
}
|
|
||||||
|
|
||||||
sysOrigin := ""
|
|
||||||
if session, ok := s.findSessionByToken(ctx, req.SSToken, req.UserID); ok {
|
|
||||||
sysOrigin = session.SysOrigin
|
|
||||||
if err := s.handleReportHighWins(ctx, session.SysOrigin, req); err != nil {
|
|
||||||
log.Printf("handle baishun report high win failed userId=%s reportType=%s: %v", req.UserID, req.ReportType, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.saveCallbackLog(
|
|
||||||
ctx,
|
|
||||||
"report",
|
|
||||||
rawJSON,
|
|
||||||
utils.MustJSONString(resp, ""),
|
|
||||||
sysOrigin,
|
|
||||||
req.UserID,
|
|
||||||
nil,
|
|
||||||
stringPtr(reportGameRoundID(req.ReportMsg)),
|
|
||||||
baishunCallbackStatusSuccess,
|
|
||||||
0,
|
|
||||||
resp.Message,
|
|
||||||
)
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaishunService) handleReportHighWins(ctx context.Context, sysOrigin string, req BaishunReportRequest) error {
|
|
||||||
switch strings.ToLower(strings.TrimSpace(req.ReportType)) {
|
|
||||||
case baishunReportTypeStart:
|
|
||||||
return s.rememberBaishunGameBets(ctx, sysOrigin, req.ReportMsg)
|
|
||||||
case baishunReportTypeSettle:
|
|
||||||
return s.publishBaishunGameHighWins(ctx, sysOrigin, req.ReportMsg)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaishunService) rememberBaishunGameBets(ctx context.Context, sysOrigin string, payload map[string]any) error {
|
|
||||||
if s == nil || s.repo.Redis == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
roomID := reportText(payload, "room_id", "roomId")
|
|
||||||
gameRoundID := reportGameRoundID(payload)
|
|
||||||
if strings.TrimSpace(roomID) == "" || strings.TrimSpace(gameRoundID) == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
gameID := reportInt64(payload, "game_id", "gameId")
|
|
||||||
for _, player := range reportPlayers(payload) {
|
|
||||||
if reportInt64(player, "is_ai", "isAi") != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
userID := reportInt64(player, "user_id", "userId")
|
|
||||||
bet := reportInt64(player, "bet")
|
|
||||||
if userID <= 0 || bet <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record := baishunGameBetRecord{
|
|
||||||
SysOrigin: normalizeAdminSysOrigin(sysOrigin),
|
|
||||||
RoomID: roomID,
|
|
||||||
GameID: gameID,
|
|
||||||
GameRoundID: gameRoundID,
|
|
||||||
UserID: userID,
|
|
||||||
Bet: bet,
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(record)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.repo.Redis.Set(ctx, baishunGameBetKey(record.SysOrigin, gameRoundID, userID), string(body), baishunReportBetTTL).Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaishunService) publishBaishunGameHighWins(ctx context.Context, sysOrigin string, payload map[string]any) error {
|
|
||||||
if s == nil || s.highWinBroadcaster == nil || s.repo.Redis == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sysOrigin = normalizeAdminSysOrigin(sysOrigin)
|
|
||||||
roomID := reportText(payload, "room_id", "roomId")
|
|
||||||
gameRoundID := reportGameRoundID(payload)
|
|
||||||
if strings.TrimSpace(roomID) == "" || strings.TrimSpace(gameRoundID) == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
gameID := reportInt64(payload, "game_id", "gameId")
|
|
||||||
gameCover := s.resolveBaishunReportGameCover(ctx, sysOrigin, roomID, gameID)
|
|
||||||
|
|
||||||
for _, player := range reportPlayers(payload) {
|
|
||||||
if reportInt64(player, "is_ai", "isAi") != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
userID := reportInt64(player, "user_id", "userId")
|
|
||||||
reward := reportInt64(player, "reward")
|
|
||||||
if userID <= 0 || reward <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
betRecord, ok, err := s.loadBaishunGameBet(ctx, sysOrigin, gameRoundID, userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ok || betRecord.Bet <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
multiple := highwin.Multiple(reward, betRecord.Bet)
|
|
||||||
if !highwin.ShouldBroadcast(multiple, reward) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"gameId": gameID,
|
|
||||||
"gameRoundId": gameRoundID,
|
|
||||||
"gameUrl": gameCover,
|
|
||||||
"betAmount": betRecord.Bet,
|
|
||||||
"currencyDiff": reward,
|
|
||||||
"rank": reportInt64(player, "rank"),
|
|
||||||
"score": reportInt64(player, "score"),
|
|
||||||
}
|
|
||||||
if err := highwin.SendRegionBroadcast(ctx, s.highWinBroadcaster, highwin.BroadcastEvent{
|
|
||||||
SysOrigin: sysOrigin,
|
|
||||||
Type: highwin.MessageTypeBaishunWin,
|
|
||||||
UserID: userID,
|
|
||||||
RoomID: roomID,
|
|
||||||
Multiple: multiple,
|
|
||||||
Amount: reward,
|
|
||||||
Data: data,
|
|
||||||
}); err != nil {
|
|
||||||
log.Printf("send baishun game high win region broadcast failed roundId=%s userId=%d: %v", gameRoundID, userID, err)
|
|
||||||
}
|
|
||||||
_ = s.repo.Redis.Del(ctx, baishunGameBetKey(sysOrigin, gameRoundID, userID)).Err()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaishunService) loadBaishunGameBet(ctx context.Context, sysOrigin, gameRoundID string, userID int64) (baishunGameBetRecord, bool, error) {
|
|
||||||
raw, err := s.repo.Redis.Get(ctx, baishunGameBetKey(sysOrigin, gameRoundID, userID)).Result()
|
|
||||||
if errors.Is(err, redis.Nil) {
|
|
||||||
return baishunGameBetRecord{}, false, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return baishunGameBetRecord{}, false, err
|
|
||||||
}
|
|
||||||
var record baishunGameBetRecord
|
|
||||||
if err := json.Unmarshal([]byte(raw), &record); err != nil {
|
|
||||||
return baishunGameBetRecord{}, false, err
|
|
||||||
}
|
|
||||||
return record, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaishunService) resolveBaishunReportGameCover(ctx context.Context, sysOrigin, roomID string, gameID int64) string {
|
|
||||||
var state model.BaishunRoomState
|
|
||||||
if strings.TrimSpace(roomID) != "" {
|
|
||||||
err := s.repo.DB.WithContext(ctx).
|
|
||||||
Where("sys_origin = ? AND room_id = ?", normalizeAdminSysOrigin(sysOrigin), roomID).
|
|
||||||
First(&state).Error
|
|
||||||
if err == nil && strings.TrimSpace(state.CurrentGameCover) != "" {
|
|
||||||
return strings.TrimSpace(state.CurrentGameCover)
|
|
||||||
}
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if gameID <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
profile, err := s.activeProfile(ctx, sysOrigin)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var catalog model.BaishunGameCatalog
|
|
||||||
if err := s.repo.DB.WithContext(ctx).
|
|
||||||
Where("sys_origin = ? AND profile = ? AND vendor_game_id = ?", normalizeAdminSysOrigin(sysOrigin), profile, gameID).
|
|
||||||
First(&catalog).Error; err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(catalog.Cover)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reportGameRoundID(payload map[string]any) string {
|
|
||||||
return reportText(payload, "game_round_id", "gameRoundId")
|
|
||||||
}
|
|
||||||
|
|
||||||
func reportPlayers(payload map[string]any) []map[string]any {
|
|
||||||
raw, exists := payload["players"]
|
|
||||||
if !exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch typed := raw.(type) {
|
|
||||||
case []map[string]any:
|
|
||||||
return typed
|
|
||||||
case []any:
|
|
||||||
players := make([]map[string]any, 0, len(typed))
|
|
||||||
for _, item := range typed {
|
|
||||||
if player, ok := item.(map[string]any); ok {
|
|
||||||
players = append(players, player)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return players
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func reportText(payload map[string]any, keys ...string) string {
|
|
||||||
for _, key := range keys {
|
|
||||||
if value, exists := payload[key]; exists {
|
|
||||||
text := strings.TrimSpace(toString(value))
|
|
||||||
if text != "" {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func reportInt64(payload map[string]any, keys ...string) int64 {
|
|
||||||
for _, key := range keys {
|
|
||||||
value, exists := payload[key]
|
|
||||||
if !exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch typed := value.(type) {
|
|
||||||
case int:
|
|
||||||
return int64(typed)
|
|
||||||
case int64:
|
|
||||||
return typed
|
|
||||||
case float64:
|
|
||||||
return int64(typed)
|
|
||||||
case json.Number:
|
|
||||||
parsed, _ := typed.Int64()
|
|
||||||
return parsed
|
|
||||||
case string:
|
|
||||||
parsed, _ := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
|
|
||||||
return parsed
|
|
||||||
default:
|
|
||||||
parsed, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(typed)), 10, 64)
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func baishunGameBetKey(sysOrigin, gameRoundID string, userID int64) string {
|
|
||||||
return strings.Join([]string{
|
|
||||||
baishunReportBetRedisPrefix,
|
|
||||||
normalizeAdminSysOrigin(sysOrigin),
|
|
||||||
strings.TrimSpace(gameRoundID),
|
|
||||||
strconv.FormatInt(userID, 10),
|
|
||||||
}, ":")
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
package baishun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHandleReportPublishesRegionHighWinAfterStartAndSettle(t *testing.T) {
|
|
||||||
service, db := newTestBaishunService(t)
|
|
||||||
mr := miniredis.RunT(t)
|
|
||||||
redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
|
||||||
t.Cleanup(func() { _ = redisClient.Close() })
|
|
||||||
service.repo.Redis = redisClient
|
|
||||||
broadcaster := &fakeBaishunRegionBroadcaster{}
|
|
||||||
service.SetHighWinBroadcaster(broadcaster)
|
|
||||||
|
|
||||||
expireAt := time.Now().Add(time.Hour)
|
|
||||||
token := "ss-token-1001"
|
|
||||||
if err := db.Create(&model.BaishunLaunchSession{
|
|
||||||
ID: 1,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
RoomID: "9001",
|
|
||||||
UserID: 1001,
|
|
||||||
InternalGameID: "bs_1146",
|
|
||||||
VendorGameID: 1146,
|
|
||||||
GameSessionID: "session-1",
|
|
||||||
SSToken: &token,
|
|
||||||
SSTokenExpireTime: &expireAt,
|
|
||||||
Status: baishunSessionActive,
|
|
||||||
CreateTime: time.Now(),
|
|
||||||
UpdateTime: time.Now(),
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("seed session: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.Create(&model.BaishunRoomState{
|
|
||||||
ID: 2,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
RoomID: "9001",
|
|
||||||
HostUserID: 1001,
|
|
||||||
CurrentGameID: "bs_1146",
|
|
||||||
CurrentVendorGameID: intPtr(1146),
|
|
||||||
CurrentGameName: "LordOfOlympus",
|
|
||||||
CurrentGameCover: "https://cdn.example.com/game.png",
|
|
||||||
GameSessionID: "session-1",
|
|
||||||
State: baishunRoomStatePlaying,
|
|
||||||
CreateTime: time.Now(),
|
|
||||||
UpdateTime: time.Now(),
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("seed room state: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
startResp := service.HandleReport(context.Background(), BaishunReportRequest{
|
|
||||||
ReportType: baishunReportTypeStart,
|
|
||||||
UserID: "1001",
|
|
||||||
SSToken: token,
|
|
||||||
ReportMsg: map[string]any{
|
|
||||||
"game_id": 1146,
|
|
||||||
"room_id": "9001",
|
|
||||||
"game_round_id": "round-1",
|
|
||||||
"players": []any{
|
|
||||||
map[string]any{"user_id": "1001", "is_ai": 0, "bet": 100},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, `{"report_type":"game_start"}`)
|
|
||||||
if startResp.Code != 0 {
|
|
||||||
t.Fatalf("start response = %+v", startResp)
|
|
||||||
}
|
|
||||||
|
|
||||||
settleResp := service.HandleReport(context.Background(), BaishunReportRequest{
|
|
||||||
ReportType: baishunReportTypeSettle,
|
|
||||||
UserID: "1001",
|
|
||||||
SSToken: token,
|
|
||||||
ReportMsg: map[string]any{
|
|
||||||
"game_id": 1146,
|
|
||||||
"room_id": "9001",
|
|
||||||
"game_round_id": "round-1",
|
|
||||||
"players": []any{
|
|
||||||
map[string]any{"user_id": "1001", "is_ai": 0, "reward": 1000, "rank": 1, "score": 88},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, `{"report_type":"game_settle"}`)
|
|
||||||
if settleResp.Code != 0 {
|
|
||||||
t.Fatalf("settle response = %+v", settleResp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(broadcaster.requests) != 1 {
|
|
||||||
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
|
||||||
}
|
|
||||||
req := broadcaster.requests[0]
|
|
||||||
if req.Type != "GAME_BAISHUN_WIN" {
|
|
||||||
t.Fatalf("type = %q, want GAME_BAISHUN_WIN", req.Type)
|
|
||||||
}
|
|
||||||
if req.Data["userId"] != "1001" || req.Data["roomId"] != "9001" ||
|
|
||||||
req.Data["multiple"] != int64(10) || req.Data["winAmount"] != int64(1000) ||
|
|
||||||
req.Data["currencyDiff"] != int64(1000) ||
|
|
||||||
req.Data["gameUrl"] != "https://cdn.example.com/game.png" {
|
|
||||||
t.Fatalf("payload = %+v", req.Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeBaishunRegionBroadcaster struct {
|
|
||||||
requests []regionimgroup.RegionBroadcastRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeBaishunRegionBroadcaster) SendRegionBroadcast(_ context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
|
||||||
f.requests = append(f.requests, req)
|
|
||||||
return ®ionimgroup.RegionBroadcastResponse{Type: req.Type}, nil
|
|
||||||
}
|
|
||||||
@ -3,7 +3,6 @@ package baishun
|
|||||||
import (
|
import (
|
||||||
"chatapp3-golang/internal/config"
|
"chatapp3-golang/internal/config"
|
||||||
"chatapp3-golang/internal/integration"
|
"chatapp3-golang/internal/integration"
|
||||||
"chatapp3-golang/internal/service/highwin"
|
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -62,12 +61,11 @@ type baishunPorts struct {
|
|||||||
|
|
||||||
// BaishunService 负责百顺目录同步、游戏启动、房间状态以及回调处理。
|
// BaishunService 负责百顺目录同步、游戏启动、房间状态以及回调处理。
|
||||||
type BaishunService struct {
|
type BaishunService struct {
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
repo baishunPorts
|
repo baishunPorts
|
||||||
java baishunGateway
|
java baishunGateway
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
taskReporter taskEventReporter
|
taskReporter taskEventReporter
|
||||||
highWinBroadcaster highwin.RegionBroadcaster
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBaishunService 创建百顺接入服务实例。
|
// NewBaishunService 创建百顺接入服务实例。
|
||||||
@ -86,10 +84,6 @@ func (s *BaishunService) SetTaskEventReporter(reporter taskEventReporter) {
|
|||||||
s.taskReporter = reporter
|
s.taskReporter = reporter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaishunService) SetHighWinBroadcaster(broadcaster highwin.RegionBroadcaster) {
|
|
||||||
s.highWinBroadcaster = broadcaster
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoomGameListItem 是房间游戏列表中的单个游戏项。
|
// RoomGameListItem 是房间游戏列表中的单个游戏项。
|
||||||
type RoomGameListItem struct {
|
type RoomGameListItem struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
@ -424,13 +418,6 @@ type BaishunBalanceInfoRequest struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaishunReportRequest struct {
|
|
||||||
ReportType string `json:"report_type"`
|
|
||||||
ReportMsg map[string]any `json:"report_msg"`
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
SSToken string `json:"ss_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// baishunStandardResponse 是百顺标准回包结构。
|
// baishunStandardResponse 是百顺标准回包结构。
|
||||||
type baishunStandardResponse struct {
|
type baishunStandardResponse struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
|
|||||||
@ -1,327 +0,0 @@
|
|||||||
package cprelationbroadcast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/common"
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProcessPayload 解码 Java MQ 消息并发送区域飘屏。
|
|
||||||
func (s *Service) ProcessPayload(ctx context.Context, payload string) (*regionimgroup.RegionBroadcastResponse, error) {
|
|
||||||
req, err := decodeEventPayload(payload, s.cfg.CPRelationBroadcast.MQ.Tag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if req.isEmpty() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return s.ProcessEvent(ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessEvent 处理标准化后的 CP 关系事件。
|
|
||||||
func (s *Service) ProcessEvent(ctx context.Context, req EventRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
|
||||||
if s == nil || s.broadcaster == nil {
|
|
||||||
return nil, common.NewAppError(http.StatusInternalServerError, "cp_relation_broadcaster_missing", "cp relation broadcaster is missing")
|
|
||||||
}
|
|
||||||
if s.db == nil {
|
|
||||||
return nil, common.NewAppError(http.StatusInternalServerError, "cp_relation_db_missing", "cp relation db is missing")
|
|
||||||
}
|
|
||||||
req = normalizeEventRequest(req)
|
|
||||||
|
|
||||||
userID := req.UserID.Int64()
|
|
||||||
cpUserID := req.CpUserID.Int64()
|
|
||||||
if userID <= 0 || cpUserID <= 0 {
|
|
||||||
return nil, common.NewAppError(http.StatusBadRequest, "invalid_cp_relation_users", "userId and cpUserId are required")
|
|
||||||
}
|
|
||||||
relationType, err := normalizeRelationType(req.RelationType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sysOrigin := normalizeSysOrigin(req.SysOrigin, s.cfg.CPRelationBroadcast.DefaultSysOrigin)
|
|
||||||
|
|
||||||
user, err := s.loadEventUser(ctx, userID, req.User)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cpUser, err := s.loadEventUser(ctx, cpUserID, req.CpUser)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := relationMessage(relationType, user.displayName(), cpUser.displayName())
|
|
||||||
data := buildBroadcastData(req, sysOrigin, relationType, user, cpUser, msg)
|
|
||||||
return s.broadcaster.SendRegionBroadcast(ctx, regionimgroup.RegionBroadcastRequest{
|
|
||||||
SysOrigin: sysOrigin,
|
|
||||||
Type: MessageTypeCPRelationBroadcast,
|
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) loadEventUser(ctx context.Context, userID int64, provided *EventUser) (EventUser, error) {
|
|
||||||
var user model.UserBaseInfo
|
|
||||||
err := s.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
account := strconv.FormatInt(userID, 10)
|
|
||||||
user = model.UserBaseInfo{
|
|
||||||
ID: userID,
|
|
||||||
Account: account,
|
|
||||||
UserNickname: account,
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return EventUser{}, err
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(user.Account) == "" {
|
|
||||||
user.Account = strconv.FormatInt(userID, 10)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(user.UserNickname) == "" {
|
|
||||||
user.UserNickname = user.Account
|
|
||||||
}
|
|
||||||
return mergeEventUser(userID, user, provided), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBroadcastData(req EventRequest, sysOrigin, relationType string, user EventUser, cpUser EventUser, msg string) map[string]any {
|
|
||||||
userID := user.normalizedID()
|
|
||||||
cpUserID := cpUser.normalizedID()
|
|
||||||
userIDText := strconv.FormatInt(userID, 10)
|
|
||||||
cpUserIDText := strconv.FormatInt(cpUserID, 10)
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"sysOrigin": sysOrigin,
|
|
||||||
"type": MessageTypeCPRelationBroadcast,
|
|
||||||
"relationType": relationType,
|
|
||||||
"relationLabel": relationLabel(relationType),
|
|
||||||
"msg": msg,
|
|
||||||
"userId": userIDText,
|
|
||||||
"sendUserId": userIDText,
|
|
||||||
"senderUserId": userIDText,
|
|
||||||
"cpUserId": cpUserIDText,
|
|
||||||
"acceptUserId": cpUserIDText,
|
|
||||||
"toUserId": cpUserIDText,
|
|
||||||
"user": user.toMap(),
|
|
||||||
"cpUser": cpUser.toMap(),
|
|
||||||
|
|
||||||
"account": user.Account,
|
|
||||||
"actualAccount": user.Account,
|
|
||||||
"userNickname": user.displayName(),
|
|
||||||
"nickname": user.displayName(),
|
|
||||||
"userAvatar": firstNonBlank(user.UserAvatar, user.Avatar),
|
|
||||||
"countryCode": user.CountryCode,
|
|
||||||
"countryName": user.CountryName,
|
|
||||||
"cpUserAccount": cpUser.Account,
|
|
||||||
"cpUserNickname": cpUser.displayName(),
|
|
||||||
"cpUserAvatar": firstNonBlank(cpUser.UserAvatar, cpUser.Avatar),
|
|
||||||
"cpUserCountryCode": cpUser.CountryCode,
|
|
||||||
"cpUserCountryName": cpUser.CountryName,
|
|
||||||
"acceptAccount": cpUser.Account,
|
|
||||||
"acceptNickname": cpUser.displayName(),
|
|
||||||
"acceptUserAvatar": firstNonBlank(cpUser.UserAvatar, cpUser.Avatar),
|
|
||||||
"toUserName": cpUser.displayName(),
|
|
||||||
"toUserAvatarUrl": firstNonBlank(cpUser.UserAvatar, cpUser.Avatar),
|
|
||||||
}
|
|
||||||
if eventID := strings.TrimSpace(req.EventID); eventID != "" {
|
|
||||||
data["eventId"] = eventID
|
|
||||||
}
|
|
||||||
if applyID := req.ApplyID.Int64(); applyID > 0 {
|
|
||||||
data["applyId"] = strconv.FormatInt(applyID, 10)
|
|
||||||
}
|
|
||||||
if occurredAt := strings.TrimSpace(req.OccurredAt); occurredAt != "" {
|
|
||||||
data["occurredAt"] = occurredAt
|
|
||||||
}
|
|
||||||
if len(req.Payload) > 0 {
|
|
||||||
data["payload"] = req.Payload
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeEventRequest(req EventRequest) EventRequest {
|
|
||||||
if req.UserID.Int64() <= 0 {
|
|
||||||
req.UserID = req.SendUserID
|
|
||||||
}
|
|
||||||
if req.CpUserID.Int64() <= 0 {
|
|
||||||
req.CpUserID = req.AcceptUserID
|
|
||||||
}
|
|
||||||
if req.UserID.Int64() <= 0 && req.User != nil {
|
|
||||||
req.UserID = flexibleInt64(req.User.normalizedID())
|
|
||||||
}
|
|
||||||
if req.CpUserID.Int64() <= 0 && req.CpUser != nil {
|
|
||||||
req.CpUserID = flexibleInt64(req.CpUser.normalizedID())
|
|
||||||
}
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req EventRequest) isEmpty() bool {
|
|
||||||
return strings.TrimSpace(req.EventID) == "" &&
|
|
||||||
strings.TrimSpace(req.RelationType) == "" &&
|
|
||||||
req.UserID.Int64() <= 0 &&
|
|
||||||
req.CpUserID.Int64() <= 0 &&
|
|
||||||
req.SendUserID.Int64() <= 0 &&
|
|
||||||
req.AcceptUserID.Int64() <= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeRelationType(value string) (string, error) {
|
|
||||||
normalized := strings.ToUpper(strings.TrimSpace(value))
|
|
||||||
if normalized == "" {
|
|
||||||
return RelationTypeCP, nil
|
|
||||||
}
|
|
||||||
switch normalized {
|
|
||||||
case RelationTypeCP:
|
|
||||||
return RelationTypeCP, nil
|
|
||||||
case RelationTypeBrother, "BROTHERS":
|
|
||||||
return RelationTypeBrother, nil
|
|
||||||
case RelationTypeSisters, "SISTER":
|
|
||||||
return RelationTypeSisters, nil
|
|
||||||
default:
|
|
||||||
return "", common.NewAppError(http.StatusBadRequest, "invalid_cp_relation_type", fmt.Sprintf("invalid cp relationType: %s", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func relationLabel(relationType string) string {
|
|
||||||
switch relationType {
|
|
||||||
case RelationTypeBrother:
|
|
||||||
return "brothers"
|
|
||||||
case RelationTypeSisters:
|
|
||||||
return "sisters"
|
|
||||||
default:
|
|
||||||
return "cp_relation"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func relationMessage(relationType, userName, cpUserName string) string {
|
|
||||||
switch relationType {
|
|
||||||
case RelationTypeBrother:
|
|
||||||
return fmt.Sprintf("%s与%s结为兄弟", userName, cpUserName)
|
|
||||||
case RelationTypeSisters:
|
|
||||||
return fmt.Sprintf("%s与%s结为姐妹", userName, cpUserName)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%s与%s get cp relation", userName, cpUserName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeSysOrigin(values ...string) string {
|
|
||||||
for _, value := range values {
|
|
||||||
if text := strings.ToUpper(strings.TrimSpace(value)); text != "" {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultSysOrigin
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeEventPayload(payload string, expectedTag string) (EventRequest, error) {
|
|
||||||
payload = strings.TrimSpace(payload)
|
|
||||||
if payload == "" {
|
|
||||||
return EventRequest{}, nil
|
|
||||||
}
|
|
||||||
req, err := decodeEventObject([]byte(payload))
|
|
||||||
if err != nil {
|
|
||||||
return EventRequest{}, err
|
|
||||||
}
|
|
||||||
if !req.isEmpty() {
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var envelope messageEventEnvelope
|
|
||||||
if err := json.Unmarshal([]byte(payload), &envelope); err != nil {
|
|
||||||
return EventRequest{}, err
|
|
||||||
}
|
|
||||||
if shouldSkipEnvelope(envelope.Tag, expectedTag) {
|
|
||||||
return EventRequest{}, nil
|
|
||||||
}
|
|
||||||
bodyPayload, ok, err := envelope.bodyPayload()
|
|
||||||
if err != nil || !ok {
|
|
||||||
return EventRequest{}, err
|
|
||||||
}
|
|
||||||
return decodeEventObject([]byte(bodyPayload))
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawEventRequest struct {
|
|
||||||
EventRequest
|
|
||||||
SysOriginSnake string `json:"sys_origin"`
|
|
||||||
EventIDSnake string `json:"event_id"`
|
|
||||||
ApplyIDSnake flexibleInt64 `json:"apply_id"`
|
|
||||||
RelationTypeSnake string `json:"relation_type"`
|
|
||||||
UserIDSnake flexibleInt64 `json:"user_id"`
|
|
||||||
CpUserIDSnake flexibleInt64 `json:"cp_user_id"`
|
|
||||||
SendUserIDSnake flexibleInt64 `json:"send_user_id"`
|
|
||||||
AcceptUserIDSnake flexibleInt64 `json:"accept_user_id"`
|
|
||||||
OccurredAtSnake string `json:"occurred_at"`
|
|
||||||
TargetUserID flexibleInt64 `json:"targetUserId"`
|
|
||||||
TargetUserIDSnake flexibleInt64 `json:"target_user_id"`
|
|
||||||
ToUserIDSnake flexibleInt64 `json:"to_user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeEventObject(data []byte) (EventRequest, error) {
|
|
||||||
var raw rawEventRequest
|
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
|
||||||
return EventRequest{}, err
|
|
||||||
}
|
|
||||||
req := raw.EventRequest
|
|
||||||
req.SysOrigin = firstNonBlank(req.SysOrigin, raw.SysOriginSnake)
|
|
||||||
req.EventID = firstNonBlank(req.EventID, raw.EventIDSnake)
|
|
||||||
req.RelationType = firstNonBlank(req.RelationType, raw.RelationTypeSnake)
|
|
||||||
req.OccurredAt = firstNonBlank(req.OccurredAt, raw.OccurredAtSnake)
|
|
||||||
if req.ApplyID.Int64() <= 0 {
|
|
||||||
req.ApplyID = raw.ApplyIDSnake
|
|
||||||
}
|
|
||||||
if req.UserID.Int64() <= 0 {
|
|
||||||
req.UserID = firstFlexible(raw.UserIDSnake, req.SendUserID, raw.SendUserIDSnake)
|
|
||||||
}
|
|
||||||
if req.CpUserID.Int64() <= 0 {
|
|
||||||
req.CpUserID = firstFlexible(raw.CpUserIDSnake, req.AcceptUserID, raw.AcceptUserIDSnake, raw.TargetUserID, raw.TargetUserIDSnake, raw.ToUserIDSnake)
|
|
||||||
}
|
|
||||||
if req.SendUserID.Int64() <= 0 {
|
|
||||||
req.SendUserID = firstFlexible(req.UserID, raw.SendUserIDSnake)
|
|
||||||
}
|
|
||||||
if req.AcceptUserID.Int64() <= 0 {
|
|
||||||
req.AcceptUserID = firstFlexible(req.CpUserID, raw.AcceptUserIDSnake, raw.TargetUserID, raw.TargetUserIDSnake, raw.ToUserIDSnake)
|
|
||||||
}
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func firstFlexible(values ...flexibleInt64) flexibleInt64 {
|
|
||||||
for _, value := range values {
|
|
||||||
if value.Int64() > 0 {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageEventEnvelope struct {
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
Body json.RawMessage `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e messageEventEnvelope) bodyPayload() (string, bool, error) {
|
|
||||||
raw := strings.TrimSpace(string(e.Body))
|
|
||||||
if raw == "" || raw == "null" {
|
|
||||||
return "", false, nil
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(raw, "\"") {
|
|
||||||
var body string
|
|
||||||
if err := json.Unmarshal(e.Body, &body); err != nil {
|
|
||||||
return "", false, err
|
|
||||||
}
|
|
||||||
body = strings.TrimSpace(body)
|
|
||||||
return body, body != "", nil
|
|
||||||
}
|
|
||||||
return raw, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldSkipEnvelope(actualTag, expectedTag string) bool {
|
|
||||||
actualTag = strings.TrimSpace(actualTag)
|
|
||||||
expectedTag = strings.TrimSpace(expectedTag)
|
|
||||||
if actualTag == "" || expectedTag == "" || expectedTag == "*" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return actualTag != expectedTag
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
package cprelationbroadcast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/config"
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProcessEventBuildsCPRelationBroadcastPayload(t *testing.T) {
|
|
||||||
service, broadcaster := newCPRelationBroadcastTestService(t)
|
|
||||||
|
|
||||||
_, err := service.ProcessEvent(context.Background(), EventRequest{
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
EventID: "CP_RELATION:9001",
|
|
||||||
ApplyID: flexibleInt64(9001),
|
|
||||||
RelationType: RelationTypeCP,
|
|
||||||
UserID: flexibleInt64(1001),
|
|
||||||
CpUserID: flexibleInt64(1002),
|
|
||||||
OccurredAt: "2026-05-22T12:00:00Z",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ProcessEvent() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(broadcaster.requests) != 1 {
|
|
||||||
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
|
||||||
}
|
|
||||||
req := broadcaster.requests[0]
|
|
||||||
if req.Type != MessageTypeCPRelationBroadcast {
|
|
||||||
t.Fatalf("type = %q", req.Type)
|
|
||||||
}
|
|
||||||
data := req.Data
|
|
||||||
if data["msg"] != "Alice与Bob get cp relation" ||
|
|
||||||
data["relationType"] != RelationTypeCP ||
|
|
||||||
data["userId"] != "1001" ||
|
|
||||||
data["cpUserId"] != "1002" {
|
|
||||||
t.Fatalf("payload = %+v", data)
|
|
||||||
}
|
|
||||||
user, ok := data["user"].(map[string]any)
|
|
||||||
if !ok || user["userNickname"] != "Alice" || user["account"] != "alice" {
|
|
||||||
t.Fatalf("user payload = %+v", data["user"])
|
|
||||||
}
|
|
||||||
cpUser, ok := data["cpUser"].(map[string]any)
|
|
||||||
if !ok || cpUser["userNickname"] != "Bob" || cpUser["account"] != "bob" {
|
|
||||||
t.Fatalf("cpUser payload = %+v", data["cpUser"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProcessEventBuildsBrotherAndSistersMessages(t *testing.T) {
|
|
||||||
service, broadcaster := newCPRelationBroadcastTestService(t)
|
|
||||||
cases := []struct {
|
|
||||||
relationType string
|
|
||||||
wantMsg string
|
|
||||||
}{
|
|
||||||
{RelationTypeBrother, "Alice与Bob结为兄弟"},
|
|
||||||
{RelationTypeSisters, "Alice与Bob结为姐妹"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
broadcaster.requests = nil
|
|
||||||
_, err := service.ProcessEvent(context.Background(), EventRequest{
|
|
||||||
RelationType: tc.relationType,
|
|
||||||
UserID: flexibleInt64(1001),
|
|
||||||
CpUserID: flexibleInt64(1002),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ProcessEvent(%s) error = %v", tc.relationType, err)
|
|
||||||
}
|
|
||||||
if len(broadcaster.requests) != 1 {
|
|
||||||
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
|
||||||
}
|
|
||||||
if got := broadcaster.requests[0].Data["msg"]; got != tc.wantMsg {
|
|
||||||
t.Fatalf("msg = %v, want %s", got, tc.wantMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProcessPayloadDecodesMessageEventEnvelopeAndAliases(t *testing.T) {
|
|
||||||
service, broadcaster := newCPRelationBroadcastTestService(t)
|
|
||||||
service.cfg.CPRelationBroadcast.MQ.Tag = "cp_relation_broadcast"
|
|
||||||
payload := `{"tag":"cp_relation_broadcast","body":"{\"sys_origin\":\"likei\",\"event_id\":\"CP_RELATION:9002\",\"apply_id\":\"9002\",\"relation_type\":\"BROTHERS\",\"send_user_id\":\"1001\",\"accept_user_id\":\"1002\"}"}`
|
|
||||||
|
|
||||||
_, err := service.ProcessPayload(context.Background(), payload)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ProcessPayload() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(broadcaster.requests) != 1 {
|
|
||||||
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
|
||||||
}
|
|
||||||
req := broadcaster.requests[0]
|
|
||||||
if req.SysOrigin != "LIKEI" {
|
|
||||||
t.Fatalf("sysOrigin = %q", req.SysOrigin)
|
|
||||||
}
|
|
||||||
if req.Data["eventId"] != "CP_RELATION:9002" ||
|
|
||||||
req.Data["applyId"] != "9002" ||
|
|
||||||
req.Data["relationType"] != RelationTypeBrother ||
|
|
||||||
req.Data["msg"] != "Alice与Bob结为兄弟" {
|
|
||||||
t.Fatalf("payload = %+v", req.Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCPRelationBroadcastTestService(t *testing.T) (*Service, *fakeCPRegionBroadcaster) {
|
|
||||||
t.Helper()
|
|
||||||
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("open sqlite: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.AutoMigrate(&model.UserBaseInfo{}); err != nil {
|
|
||||||
t.Fatalf("auto migrate: %v", err)
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
if err := db.Create(&[]model.UserBaseInfo{
|
|
||||||
{ID: 1001, Account: "alice", UserNickname: "Alice", UserAvatar: "alice.png", OriginSys: "LIKEI", CountryCode: "SA", CountryName: "Saudi Arabia", CreateTime: now},
|
|
||||||
{ID: 1002, Account: "bob", UserNickname: "Bob", UserAvatar: "bob.png", OriginSys: "LIKEI", CountryCode: "SA", CountryName: "Saudi Arabia", CreateTime: now},
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("seed users: %v", err)
|
|
||||||
}
|
|
||||||
broadcaster := &fakeCPRegionBroadcaster{}
|
|
||||||
return NewService(config.Config{
|
|
||||||
CPRelationBroadcast: config.CPRelationBroadcastConfig{DefaultSysOrigin: "LIKEI"},
|
|
||||||
}, db, broadcaster), broadcaster
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeCPRegionBroadcaster struct {
|
|
||||||
requests []regionimgroup.RegionBroadcastRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeCPRegionBroadcaster) SendRegionBroadcast(_ context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
|
||||||
f.requests = append(f.requests, req)
|
|
||||||
return ®ionimgroup.RegionBroadcastResponse{
|
|
||||||
SysOrigin: req.SysOrigin,
|
|
||||||
RegionCode: "AR",
|
|
||||||
GroupID: "@region-ar",
|
|
||||||
Type: req.Type,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
package cprelationbroadcast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/common"
|
|
||||||
|
|
||||||
rmq "github.com/apache/rocketmq-clients/golang/v5"
|
|
||||||
"github.com/apache/rocketmq-clients/golang/v5/credentials"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StartMessageConsumer 启动 CP 关系成功 MQ 消费链路。
|
|
||||||
func (s *Service) StartMessageConsumer(ctx context.Context) error {
|
|
||||||
if !s.cfg.CPRelationBroadcast.MQ.Enabled {
|
|
||||||
log.Printf("cp relation broadcast rocketmq consumer disabled")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(s.cfg.CPRelationBroadcast.MQ.Endpoint) == "" ||
|
|
||||||
strings.TrimSpace(s.cfg.CPRelationBroadcast.MQ.AccessKey) == "" ||
|
|
||||||
strings.TrimSpace(s.cfg.CPRelationBroadcast.MQ.AccessSecret) == "" ||
|
|
||||||
strings.TrimSpace(s.cfg.CPRelationBroadcast.MQ.Topic) == "" ||
|
|
||||||
strings.TrimSpace(s.cfg.CPRelationBroadcast.MQ.ConsumerGroup) == "" {
|
|
||||||
log.Printf("cp relation broadcast rocketmq consumer skipped: endpoint, credentials, topic or consumer group missing")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
go s.startRocketMQConsumerWithRetry(ctx)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) startRocketMQConsumerWithRetry(ctx context.Context) {
|
|
||||||
retryDelay := 5 * time.Second
|
|
||||||
for {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := s.startRocketMQConsumer(ctx); err != nil {
|
|
||||||
log.Printf("start cp relation broadcast rocketmq consumer failed: %v; retry in %s", err, retryDelay)
|
|
||||||
timer := time.NewTimer(retryDelay)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
timer.Stop()
|
|
||||||
return
|
|
||||||
case <-timer.C:
|
|
||||||
}
|
|
||||||
if retryDelay < time.Minute {
|
|
||||||
retryDelay *= 2
|
|
||||||
if retryDelay > time.Minute {
|
|
||||||
retryDelay = time.Minute
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) startRocketMQConsumer(ctx context.Context) error {
|
|
||||||
filter := rmq.SUB_ALL
|
|
||||||
if tag := strings.TrimSpace(s.cfg.CPRelationBroadcast.MQ.Tag); tag != "" && tag != "*" {
|
|
||||||
filter = rmq.NewFilterExpression(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
consumer, err := rmq.NewPushConsumer(&rmq.Config{
|
|
||||||
Endpoint: s.cfg.CPRelationBroadcast.MQ.Endpoint,
|
|
||||||
NameSpace: s.cfg.CPRelationBroadcast.MQ.Namespace,
|
|
||||||
ConsumerGroup: s.cfg.CPRelationBroadcast.MQ.ConsumerGroup,
|
|
||||||
Credentials: &credentials.SessionCredentials{
|
|
||||||
AccessKey: s.cfg.CPRelationBroadcast.MQ.AccessKey,
|
|
||||||
AccessSecret: s.cfg.CPRelationBroadcast.MQ.AccessSecret,
|
|
||||||
SecurityToken: s.cfg.CPRelationBroadcast.MQ.SecurityToken,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rmq.WithPushSubscriptionExpressions(map[string]*rmq.FilterExpression{
|
|
||||||
s.cfg.CPRelationBroadcast.MQ.Topic: filter,
|
|
||||||
}),
|
|
||||||
rmq.WithPushConsumptionThreadCount(4),
|
|
||||||
rmq.WithPushMaxCacheMessageCount(256),
|
|
||||||
rmq.WithPushMessageListener(&rmq.FuncMessageListener{
|
|
||||||
Consume: func(messageView *rmq.MessageView) rmq.ConsumerResult {
|
|
||||||
if messageView == nil {
|
|
||||||
return rmq.SUCCESS
|
|
||||||
}
|
|
||||||
if err := s.processRocketMQMessage(context.Background(), messageView); err != nil {
|
|
||||||
log.Printf("cp relation broadcast mq process failed. messageId=%s err=%v", messageView.GetMessageId(), err)
|
|
||||||
return rmq.FAILURE
|
|
||||||
}
|
|
||||||
return rmq.SUCCESS
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := consumer.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.rocketConsumer = consumer
|
|
||||||
log.Printf("cp relation broadcast rocketmq consumer started. topic=%s group=%s tag=%s",
|
|
||||||
s.cfg.CPRelationBroadcast.MQ.Topic,
|
|
||||||
s.cfg.CPRelationBroadcast.MQ.ConsumerGroup,
|
|
||||||
s.cfg.CPRelationBroadcast.MQ.Tag,
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
if s.rocketConsumer != nil {
|
|
||||||
_ = s.rocketConsumer.GracefulStop()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) processRocketMQMessage(ctx context.Context, messageView *rmq.MessageView) error {
|
|
||||||
payload := strings.TrimSpace(string(messageView.GetBody()))
|
|
||||||
if payload == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if _, err := s.ProcessPayload(ctx, payload); err != nil {
|
|
||||||
var syntaxErr *json.SyntaxError
|
|
||||||
var typeErr *json.UnmarshalTypeError
|
|
||||||
if errors.As(err, &syntaxErr) || errors.As(err, &typeErr) {
|
|
||||||
log.Printf("drop malformed cp relation broadcast message. messageId=%s err=%v", messageView.GetMessageId(), err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var appErr *common.AppError
|
|
||||||
if errors.As(err, &appErr) && appErr.Status >= http.StatusBadRequest && appErr.Status < http.StatusInternalServerError {
|
|
||||||
log.Printf("drop invalid cp relation broadcast message. messageId=%s code=%s err=%v", messageView.GetMessageId(), appErr.Code, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
package cprelationbroadcast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/config"
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
|
||||||
|
|
||||||
rmq "github.com/apache/rocketmq-clients/golang/v5"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
MessageTypeCPRelationBroadcast = "CP_RELATION_BROADCAST"
|
|
||||||
|
|
||||||
RelationTypeCP = "CP"
|
|
||||||
RelationTypeBrother = "BROTHER"
|
|
||||||
RelationTypeSisters = "SISTERS"
|
|
||||||
|
|
||||||
defaultSysOrigin = "LIKEI"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dbHandle interface {
|
|
||||||
WithContext(ctx context.Context) *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
type regionBroadcaster interface {
|
|
||||||
SendRegionBroadcast(ctx context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service 消费 Java CP 关系成功 MQ,并复用区域 IM 广播发送飘屏。
|
|
||||||
type Service struct {
|
|
||||||
cfg config.Config
|
|
||||||
db dbHandle
|
|
||||||
broadcaster regionBroadcaster
|
|
||||||
rocketConsumer rmq.PushConsumer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewService(cfg config.Config, db dbHandle, broadcaster regionBroadcaster) *Service {
|
|
||||||
return &Service{cfg: cfg, db: db, broadcaster: broadcaster}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventRequest 是 Java 在 CP/兄弟/姐妹关系达成后投递给 Go 的 MQ 事件。
|
|
||||||
type EventRequest struct {
|
|
||||||
SysOrigin string `json:"sysOrigin"`
|
|
||||||
EventID string `json:"eventId"`
|
|
||||||
ApplyID flexibleInt64 `json:"applyId"`
|
|
||||||
RelationType string `json:"relationType"`
|
|
||||||
UserID flexibleInt64 `json:"userId"`
|
|
||||||
CpUserID flexibleInt64 `json:"cpUserId"`
|
|
||||||
SendUserID flexibleInt64 `json:"sendUserId"`
|
|
||||||
AcceptUserID flexibleInt64 `json:"acceptUserId"`
|
|
||||||
OccurredAt string `json:"occurredAt"`
|
|
||||||
User *EventUser `json:"user"`
|
|
||||||
CpUser *EventUser `json:"cpUser"`
|
|
||||||
Payload map[string]any `json:"payload,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventUser struct {
|
|
||||||
ID flexibleInt64 `json:"id"`
|
|
||||||
UserID flexibleInt64 `json:"userId"`
|
|
||||||
Account string `json:"account"`
|
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
UserNickname string `json:"userNickname"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
UserAvatar string `json:"userAvatar"`
|
|
||||||
CountryCode string `json:"countryCode"`
|
|
||||||
CountryName string `json:"countryName"`
|
|
||||||
OriginSys string `json:"originSys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u EventUser) normalizedID() int64 {
|
|
||||||
if id := u.UserID.Int64(); id > 0 {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
return u.ID.Int64()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u EventUser) displayName() string {
|
|
||||||
for _, value := range []string{u.UserNickname, u.Nickname, u.Account} {
|
|
||||||
if text := strings.TrimSpace(value); text != "" {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if id := u.normalizedID(); id > 0 {
|
|
||||||
return strconv.FormatInt(id, 10)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u EventUser) toMap() map[string]any {
|
|
||||||
id := u.normalizedID()
|
|
||||||
data := map[string]any{}
|
|
||||||
if id > 0 {
|
|
||||||
data["userId"] = strconv.FormatInt(id, 10)
|
|
||||||
data["id"] = strconv.FormatInt(id, 10)
|
|
||||||
}
|
|
||||||
if account := strings.TrimSpace(u.Account); account != "" {
|
|
||||||
data["account"] = account
|
|
||||||
}
|
|
||||||
if nickname := strings.TrimSpace(firstNonBlank(u.UserNickname, u.Nickname)); nickname != "" {
|
|
||||||
data["userNickname"] = nickname
|
|
||||||
data["nickname"] = nickname
|
|
||||||
}
|
|
||||||
if avatar := strings.TrimSpace(firstNonBlank(u.UserAvatar, u.Avatar)); avatar != "" {
|
|
||||||
data["userAvatar"] = avatar
|
|
||||||
data["avatar"] = avatar
|
|
||||||
}
|
|
||||||
if countryCode := strings.TrimSpace(u.CountryCode); countryCode != "" {
|
|
||||||
data["countryCode"] = countryCode
|
|
||||||
}
|
|
||||||
if countryName := strings.TrimSpace(u.CountryName); countryName != "" {
|
|
||||||
data["countryName"] = countryName
|
|
||||||
}
|
|
||||||
if originSys := strings.TrimSpace(u.OriginSys); originSys != "" {
|
|
||||||
data["originSys"] = originSys
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventUserFromModel(user model.UserBaseInfo) EventUser {
|
|
||||||
return EventUser{
|
|
||||||
ID: flexibleInt64(user.ID),
|
|
||||||
UserID: flexibleInt64(user.ID),
|
|
||||||
Account: user.Account,
|
|
||||||
Nickname: user.UserNickname,
|
|
||||||
UserNickname: user.UserNickname,
|
|
||||||
Avatar: user.UserAvatar,
|
|
||||||
UserAvatar: user.UserAvatar,
|
|
||||||
CountryCode: user.CountryCode,
|
|
||||||
CountryName: user.CountryName,
|
|
||||||
OriginSys: user.OriginSys,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeEventUser(id int64, dbUser model.UserBaseInfo, provided *EventUser) EventUser {
|
|
||||||
merged := eventUserFromModel(dbUser)
|
|
||||||
if id > 0 {
|
|
||||||
merged.ID = flexibleInt64(id)
|
|
||||||
merged.UserID = flexibleInt64(id)
|
|
||||||
}
|
|
||||||
if provided == nil {
|
|
||||||
return merged
|
|
||||||
}
|
|
||||||
if provided.normalizedID() > 0 {
|
|
||||||
merged.ID = flexibleInt64(provided.normalizedID())
|
|
||||||
merged.UserID = flexibleInt64(provided.normalizedID())
|
|
||||||
}
|
|
||||||
merged.Account = firstNonBlank(provided.Account, merged.Account)
|
|
||||||
merged.UserNickname = firstNonBlank(provided.UserNickname, provided.Nickname, merged.UserNickname)
|
|
||||||
merged.Nickname = merged.UserNickname
|
|
||||||
merged.UserAvatar = firstNonBlank(provided.UserAvatar, provided.Avatar, merged.UserAvatar)
|
|
||||||
merged.Avatar = merged.UserAvatar
|
|
||||||
merged.CountryCode = firstNonBlank(provided.CountryCode, merged.CountryCode)
|
|
||||||
merged.CountryName = firstNonBlank(provided.CountryName, merged.CountryName)
|
|
||||||
merged.OriginSys = firstNonBlank(provided.OriginSys, merged.OriginSys)
|
|
||||||
return merged
|
|
||||||
}
|
|
||||||
|
|
||||||
type flexibleInt64 int64
|
|
||||||
|
|
||||||
func (v *flexibleInt64) UnmarshalJSON(data []byte) error {
|
|
||||||
raw := strings.TrimSpace(string(data))
|
|
||||||
switch raw {
|
|
||||||
case "", "null", `""`:
|
|
||||||
*v = 0
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(raw, `"`) && strings.HasSuffix(raw, `"`) {
|
|
||||||
unquoted, err := strconv.Unquote(raw)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
raw = strings.TrimSpace(unquoted)
|
|
||||||
}
|
|
||||||
parsed, err := strconv.ParseInt(raw, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid int64 value: %w", err)
|
|
||||||
}
|
|
||||||
*v = flexibleInt64(parsed)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v flexibleInt64) Int64() int64 {
|
|
||||||
return int64(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func firstNonBlank(values ...string) string {
|
|
||||||
for _, value := range values {
|
|
||||||
if text := strings.TrimSpace(value); text != "" {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
package highwin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ThresholdMultiple = 10
|
|
||||||
|
|
||||||
MessageTypeLuckyGift = "GAME_LUCKY_GIFT"
|
|
||||||
MessageTypeBaishunWin = "GAME_BAISHUN_WIN"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RegionBroadcaster interface {
|
|
||||||
SendRegionBroadcast(ctx context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type BroadcastEvent struct {
|
|
||||||
SysOrigin string
|
|
||||||
Type string
|
|
||||||
UserID int64
|
|
||||||
RoomID string
|
|
||||||
Multiple float64
|
|
||||||
Amount int64
|
|
||||||
Data map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
func IntegralMultiple(amount, base int64) int64 {
|
|
||||||
if amount <= 0 || base <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return amount / base
|
|
||||||
}
|
|
||||||
|
|
||||||
func Multiple(amount, base int64) float64 {
|
|
||||||
if amount <= 0 || base <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return float64(amount) / float64(base)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ShouldBroadcast(multiple float64, amount int64) bool {
|
|
||||||
return amount > 0 && multiple >= ThresholdMultiple
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendRegionBroadcast(ctx context.Context, broadcaster RegionBroadcaster, event BroadcastEvent) error {
|
|
||||||
if broadcaster == nil || !ShouldBroadcast(event.Multiple, event.Amount) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
messageType := strings.TrimSpace(event.Type)
|
|
||||||
if messageType == "" {
|
|
||||||
return fmt.Errorf("high win broadcast type is required")
|
|
||||||
}
|
|
||||||
if event.UserID <= 0 {
|
|
||||||
return fmt.Errorf("high win broadcast user id is required")
|
|
||||||
}
|
|
||||||
roomID := strings.TrimSpace(event.RoomID)
|
|
||||||
if roomID == "" {
|
|
||||||
return fmt.Errorf("high win broadcast room id is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
data := cloneData(event.Data)
|
|
||||||
userIDText := strconv.FormatInt(event.UserID, 10)
|
|
||||||
setRequired(data, "userId", userIDText)
|
|
||||||
setRequired(data, "sendUserId", userIDText)
|
|
||||||
setRequired(data, "senderUserId", userIDText)
|
|
||||||
setRequired(data, "roomId", roomID)
|
|
||||||
setRequired(data, "multiple", normalizeMultiple(event.Multiple))
|
|
||||||
setRequired(data, "winMultiple", normalizeMultiple(event.Multiple))
|
|
||||||
setRequired(data, "winAmount", event.Amount)
|
|
||||||
setRequired(data, "awardAmount", event.Amount)
|
|
||||||
setRequired(data, "msg", highWinMessage(data, messageType, userIDText, event.Multiple, event.Amount))
|
|
||||||
|
|
||||||
_, err := broadcaster.SendRegionBroadcast(ctx, regionimgroup.RegionBroadcastRequest{
|
|
||||||
SysOrigin: strings.TrimSpace(event.SysOrigin),
|
|
||||||
Type: messageType,
|
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneData(data map[string]any) map[string]any {
|
|
||||||
clone := map[string]any{}
|
|
||||||
for key, value := range data {
|
|
||||||
clone[key] = value
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRequired(data map[string]any, key string, value any) {
|
|
||||||
data[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeMultiple(value float64) any {
|
|
||||||
if value <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if math.Trunc(value) == value {
|
|
||||||
return int64(value)
|
|
||||||
}
|
|
||||||
return math.Round(value*100) / 100
|
|
||||||
}
|
|
||||||
|
|
||||||
func highWinMessage(data map[string]any, messageType, fallbackUser string, multiple float64, amount int64) string {
|
|
||||||
user := firstTextValue(data, "userNickname", "nickname", "account", "actualAccount", "sendUserId", "senderUserId", "userId")
|
|
||||||
if user == "" {
|
|
||||||
user = fallbackUser
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s play %s get x %s win %d coins!!!", user, highWinPlayTarget(messageType), formatMultipleText(multiple), amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func highWinPlayTarget(messageType string) string {
|
|
||||||
switch strings.TrimSpace(messageType) {
|
|
||||||
case MessageTypeLuckyGift:
|
|
||||||
return "lucky_gift"
|
|
||||||
case MessageTypeBaishunWin:
|
|
||||||
return "game"
|
|
||||||
default:
|
|
||||||
return "game"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func firstTextValue(data map[string]any, keys ...string) string {
|
|
||||||
for _, key := range keys {
|
|
||||||
if value := strings.TrimSpace(fmt.Sprint(data[key])); value != "" && value != "<nil>" {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatMultipleText(value float64) string {
|
|
||||||
normalized := normalizeMultiple(value)
|
|
||||||
switch typed := normalized.(type) {
|
|
||||||
case int64:
|
|
||||||
return strconv.FormatInt(typed, 10)
|
|
||||||
case float64:
|
|
||||||
return strconv.FormatFloat(typed, 'f', -1, 64)
|
|
||||||
default:
|
|
||||||
return strings.TrimSpace(fmt.Sprint(typed))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
package highwin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSendRegionBroadcastBuildsRequiredPayload(t *testing.T) {
|
|
||||||
broadcaster := &fakeRegionBroadcaster{}
|
|
||||||
|
|
||||||
err := SendRegionBroadcast(context.Background(), broadcaster, BroadcastEvent{
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
Type: MessageTypeBaishunWin,
|
|
||||||
UserID: 1001,
|
|
||||||
RoomID: "9001",
|
|
||||||
Multiple: 10.5,
|
|
||||||
Amount: 2100,
|
|
||||||
Data: map[string]any{
|
|
||||||
"gameId": 1146,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SendRegionBroadcast() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(broadcaster.requests) != 1 {
|
|
||||||
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
|
||||||
}
|
|
||||||
req := broadcaster.requests[0]
|
|
||||||
if req.Type != MessageTypeBaishunWin {
|
|
||||||
t.Fatalf("type = %q, want %q", req.Type, MessageTypeBaishunWin)
|
|
||||||
}
|
|
||||||
if req.Data["userId"] != "1001" || req.Data["roomId"] != "9001" ||
|
|
||||||
req.Data["winAmount"] != int64(2100) || req.Data["multiple"] != 10.5 ||
|
|
||||||
req.Data["msg"] != "1001 play game get x 10.5 win 2100 coins!!!" {
|
|
||||||
t.Fatalf("payload = %+v", req.Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSendRegionBroadcastSkipsBelowThreshold(t *testing.T) {
|
|
||||||
broadcaster := &fakeRegionBroadcaster{}
|
|
||||||
|
|
||||||
err := SendRegionBroadcast(context.Background(), broadcaster, BroadcastEvent{
|
|
||||||
Type: MessageTypeLuckyGift,
|
|
||||||
UserID: 1001,
|
|
||||||
RoomID: "9001",
|
|
||||||
Multiple: 9,
|
|
||||||
Amount: 900,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SendRegionBroadcast() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(broadcaster.requests) != 0 {
|
|
||||||
t.Fatalf("requests = %d, want 0", len(broadcaster.requests))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeRegionBroadcaster struct {
|
|
||||||
requests []regionimgroup.RegionBroadcastRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeRegionBroadcaster) SendRegionBroadcast(_ context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
|
||||||
f.requests = append(f.requests, req)
|
|
||||||
return ®ionimgroup.RegionBroadcastResponse{
|
|
||||||
SysOrigin: req.SysOrigin,
|
|
||||||
RegionCode: "AR",
|
|
||||||
GroupID: "@region-ar",
|
|
||||||
Type: req.Type,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@ -140,8 +140,6 @@ func (s *LuckyGiftService) Draw(ctx context.Context, req LuckyGiftDrawRequest) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.publishHighWinRegionBroadcasts(ctx, req, results, balanceAfter)
|
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
package luckygift
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/service/highwin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *LuckyGiftService) publishHighWinRegionBroadcasts(ctx context.Context, req LuckyGiftDrawRequest, results []LuckyGiftDrawResult, balanceAfter int64) {
|
|
||||||
if s == nil || s.highWinBroadcaster == nil || req.GiftPrice <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, result := range results {
|
|
||||||
if result.RewardNum <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
multiple := highwin.IntegralMultiple(result.RewardNum, req.GiftPrice)
|
|
||||||
if !highwin.ShouldBroadcast(float64(multiple), result.RewardNum) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"businessId": req.BusinessID,
|
|
||||||
"orderId": result.ID,
|
|
||||||
"providerOrderId": result.OrderID,
|
|
||||||
"acceptUserId": strconv.FormatInt(result.AcceptUserID, 10),
|
|
||||||
"giftId": strconv.FormatInt(req.GiftID, 10),
|
|
||||||
"giftCandy": req.GiftPrice,
|
|
||||||
"giftQuantity": req.GiftNum,
|
|
||||||
"multipleType": luckyGiftMultipleType(multiple),
|
|
||||||
"balance": balanceAfter,
|
|
||||||
"globalNews": true,
|
|
||||||
}
|
|
||||||
if err := highwin.SendRegionBroadcast(ctx, s.highWinBroadcaster, highwin.BroadcastEvent{
|
|
||||||
SysOrigin: req.SysOrigin,
|
|
||||||
Type: highwin.MessageTypeLuckyGift,
|
|
||||||
UserID: req.SendUserID,
|
|
||||||
RoomID: strconv.FormatInt(req.RoomID, 10),
|
|
||||||
Multiple: float64(multiple),
|
|
||||||
Amount: result.RewardNum,
|
|
||||||
Data: data,
|
|
||||||
}); err != nil {
|
|
||||||
log.Printf("send lucky gift high win region broadcast failed businessId=%s orderId=%s: %v", req.BusinessID, result.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func luckyGiftMultipleType(_ int64) string {
|
|
||||||
return "WIN"
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
package luckygift
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPublishHighWinRegionBroadcastsSendsOnlyTenTimesAndAbove(t *testing.T) {
|
|
||||||
broadcaster := &fakeLuckyGiftRegionBroadcaster{}
|
|
||||||
service := &LuckyGiftService{highWinBroadcaster: broadcaster}
|
|
||||||
|
|
||||||
service.publishHighWinRegionBroadcasts(context.Background(), LuckyGiftDrawRequest{
|
|
||||||
BusinessID: "biz-1",
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
RoomID: 9001,
|
|
||||||
SendUserID: 1001,
|
|
||||||
GiftID: 5001,
|
|
||||||
GiftPrice: 100,
|
|
||||||
GiftNum: 1,
|
|
||||||
}, []LuckyGiftDrawResult{
|
|
||||||
{ID: "low", AcceptUserID: 2001, RewardNum: 900},
|
|
||||||
{ID: "high", OrderID: "LG_high", AcceptUserID: 2002, RewardNum: 1000},
|
|
||||||
}, 90000)
|
|
||||||
|
|
||||||
if len(broadcaster.requests) != 1 {
|
|
||||||
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
|
||||||
}
|
|
||||||
req := broadcaster.requests[0]
|
|
||||||
if req.Type != "GAME_LUCKY_GIFT" {
|
|
||||||
t.Fatalf("type = %q, want GAME_LUCKY_GIFT", req.Type)
|
|
||||||
}
|
|
||||||
if req.Data["sendUserId"] != "1001" || req.Data["roomId"] != "9001" ||
|
|
||||||
req.Data["multiple"] != int64(10) || req.Data["awardAmount"] != int64(1000) ||
|
|
||||||
req.Data["acceptUserId"] != "2002" {
|
|
||||||
t.Fatalf("payload = %+v", req.Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeLuckyGiftRegionBroadcaster struct {
|
|
||||||
requests []regionimgroup.RegionBroadcastRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeLuckyGiftRegionBroadcaster) SendRegionBroadcast(_ context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
|
||||||
f.requests = append(f.requests, req)
|
|
||||||
return ®ionimgroup.RegionBroadcastResponse{Type: req.Type}, nil
|
|
||||||
}
|
|
||||||
@ -3,7 +3,6 @@ package luckygift
|
|||||||
import (
|
import (
|
||||||
"chatapp3-golang/internal/config"
|
"chatapp3-golang/internal/config"
|
||||||
"chatapp3-golang/internal/integration"
|
"chatapp3-golang/internal/integration"
|
||||||
"chatapp3-golang/internal/service/highwin"
|
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@ -40,11 +39,10 @@ type luckyGiftPorts struct {
|
|||||||
|
|
||||||
// LuckyGiftService 负责幸运礼物抽奖、三方调用和钱包返奖。
|
// LuckyGiftService 负责幸运礼物抽奖、三方调用和钱包返奖。
|
||||||
type LuckyGiftService struct {
|
type LuckyGiftService struct {
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
repo luckyGiftPorts
|
repo luckyGiftPorts
|
||||||
java luckyGiftGateway
|
java luckyGiftGateway
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
highWinBroadcaster highwin.RegionBroadcaster
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LuckyGiftDrawRequest 是幸运礼物抽奖入参。
|
// LuckyGiftDrawRequest 是幸运礼物抽奖入参。
|
||||||
@ -62,10 +60,6 @@ type LuckyGiftDrawRequest struct {
|
|||||||
Orders []LuckyGiftDrawOrderInput `json:"orders"`
|
Orders []LuckyGiftDrawOrderInput `json:"orders"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LuckyGiftService) SetHighWinBroadcaster(broadcaster highwin.RegionBroadcaster) {
|
|
||||||
s.highWinBroadcaster = broadcaster
|
|
||||||
}
|
|
||||||
|
|
||||||
// LuckyGiftDrawOrderInput 表示单个收礼人的抽奖子单。
|
// LuckyGiftDrawOrderInput 表示单个收礼人的抽奖子单。
|
||||||
type LuckyGiftDrawOrderInput struct {
|
type LuckyGiftDrawOrderInput struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|||||||
@ -1,367 +0,0 @@
|
|||||||
package managercenter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/utils"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type bdLeaderRow struct {
|
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
|
||||||
Origin string `gorm:"column:sys_origin"`
|
|
||||||
UserID int64 `gorm:"column:user_id"`
|
|
||||||
Contact string `gorm:"column:contact"`
|
|
||||||
RegionID string `gorm:"column:region_id"`
|
|
||||||
CreateTime time.Time `gorm:"column:create_time"`
|
|
||||||
UpdateTime time.Time `gorm:"column:update_time"`
|
|
||||||
CreateUser int64 `gorm:"column:create_user"`
|
|
||||||
UpdateUser int64 `gorm:"column:update_user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bdLeaderRow) TableName() string {
|
|
||||||
return "room_bd_lead_base_info"
|
|
||||||
}
|
|
||||||
|
|
||||||
type businessDevelopmentBaseInfoRow struct {
|
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
|
||||||
Origin string `gorm:"column:sys_origin"`
|
|
||||||
BDLeadUserID *int64 `gorm:"column:bd_lead_user_id"`
|
|
||||||
UserID int64 `gorm:"column:user_id"`
|
|
||||||
Contact string `gorm:"column:contact"`
|
|
||||||
Region string `gorm:"column:region"`
|
|
||||||
MemberQuantity int64 `gorm:"column:member_quantity"`
|
|
||||||
CreateTime time.Time `gorm:"column:create_time"`
|
|
||||||
UpdateTime time.Time `gorm:"column:update_time"`
|
|
||||||
CreateUser int64 `gorm:"column:create_user"`
|
|
||||||
UpdateUser int64 `gorm:"column:update_user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (businessDevelopmentBaseInfoRow) TableName() string {
|
|
||||||
return "room_business_development_base_info"
|
|
||||||
}
|
|
||||||
|
|
||||||
type userHistoryIdentityRow struct {
|
|
||||||
UserID int64 `gorm:"column:user_id;primaryKey"`
|
|
||||||
IsBD bool `gorm:"column:is_bd"`
|
|
||||||
IsAgent bool `gorm:"column:is_agent"`
|
|
||||||
IsHost bool `gorm:"column:is_host"`
|
|
||||||
IsManager bool `gorm:"column:is_manager"`
|
|
||||||
CreateTime time.Time `gorm:"column:create_time"`
|
|
||||||
UpdateTime time.Time `gorm:"column:update_time"`
|
|
||||||
CreateUser int64 `gorm:"column:create_user"`
|
|
||||||
UpdateUser int64 `gorm:"column:update_user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (userHistoryIdentityRow) TableName() string {
|
|
||||||
return "user_history_identity"
|
|
||||||
}
|
|
||||||
|
|
||||||
type administratorRow struct {
|
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
|
||||||
UserID int64 `gorm:"column:user_id"`
|
|
||||||
Status bool `gorm:"column:status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (administratorRow) TableName() string {
|
|
||||||
return "sys_administrator"
|
|
||||||
}
|
|
||||||
|
|
||||||
type bdLeaderListRow struct {
|
|
||||||
ID int64 `gorm:"column:id"`
|
|
||||||
UserID int64 `gorm:"column:user_id"`
|
|
||||||
Origin string `gorm:"column:sys_origin"`
|
|
||||||
Contact string `gorm:"column:contact"`
|
|
||||||
RegionID string `gorm:"column:region_id"`
|
|
||||||
CreateTime time.Time `gorm:"column:create_time"`
|
|
||||||
UpdateTime time.Time `gorm:"column:update_time"`
|
|
||||||
Account string `gorm:"column:account"`
|
|
||||||
UserAvatar string `gorm:"column:user_avatar"`
|
|
||||||
UserNickname string `gorm:"column:user_nickname"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) ListBDLeaders(ctx context.Context, user AuthUser) (*BDLeaderListResponse, error) {
|
|
||||||
manager, err := s.requireManagerInfo(ctx, user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
query := s.db.WithContext(ctx).
|
|
||||||
Table("room_bd_lead_base_info AS leader").
|
|
||||||
Select(strings.Join([]string{
|
|
||||||
"leader.id",
|
|
||||||
"leader.user_id",
|
|
||||||
"leader.sys_origin",
|
|
||||||
"leader.contact",
|
|
||||||
"leader.region_id",
|
|
||||||
"leader.create_time",
|
|
||||||
"leader.update_time",
|
|
||||||
"user_info.account",
|
|
||||||
"user_info.user_avatar",
|
|
||||||
"user_info.user_nickname",
|
|
||||||
}, ", ")).
|
|
||||||
Joins("LEFT JOIN user_base_info AS user_info ON user_info.id = leader.user_id AND user_info.is_del = ?", false).
|
|
||||||
Where("leader.create_user = ?", user.UserID)
|
|
||||||
|
|
||||||
if origin := managerSysOrigin(manager, user); origin != "" {
|
|
||||||
query = query.Where("leader.sys_origin = ?", origin)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows []bdLeaderListRow
|
|
||||||
if err := query.Order("leader.create_time DESC, leader.id DESC").Find(&rows).Error; err != nil {
|
|
||||||
return nil, serverError("bd_leader_query_failed", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
records := make([]BDLeaderView, 0, len(rows))
|
|
||||||
for _, row := range rows {
|
|
||||||
records = append(records, bdLeaderListRowToView(row))
|
|
||||||
}
|
|
||||||
return &BDLeaderListResponse{Count: len(records), Records: records}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) AddBDLeader(ctx context.Context, user AuthUser, req AddBDLeaderRequest) (*AddBDLeaderResponse, error) {
|
|
||||||
manager, err := s.requireManagerInfo(ctx, user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if req.UserID.Int64() <= 0 {
|
|
||||||
return nil, badRequest("user_required", "userId is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
target, err := s.loadUserRowByID(ctx, req.UserID.Int64())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, notFound("user_not_found", "user not found")
|
|
||||||
}
|
|
||||||
return nil, serverError("user_query_failed", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
sysOrigin := managerSysOrigin(manager, user)
|
|
||||||
if sysOrigin == "" {
|
|
||||||
sysOrigin = normalizeSysOrigin(target.OriginSys)
|
|
||||||
}
|
|
||||||
if target.OriginSys != "" && sysOrigin != "" && !strings.EqualFold(target.OriginSys, sysOrigin) {
|
|
||||||
return nil, badRequest("bd_leader_origin_mismatch", "target user sysOrigin does not match manager")
|
|
||||||
}
|
|
||||||
|
|
||||||
regionID := strings.TrimSpace(req.RegionID)
|
|
||||||
if regionID == "" {
|
|
||||||
regionID = strings.TrimSpace(manager.RegionID)
|
|
||||||
}
|
|
||||||
if regionID == "" {
|
|
||||||
return nil, badRequest("manager_region_required", "manager region is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists, err := s.userIsAdministrator(ctx, target.ID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if exists {
|
|
||||||
return nil, badRequest("user_is_admin", "administrator cannot be added as BD leader")
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists, err := s.userIsBDLeader(ctx, target.ID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if exists {
|
|
||||||
return nil, badRequest("user_already_bd_leader", "user is already a BD leader")
|
|
||||||
}
|
|
||||||
|
|
||||||
leaderID, err := utils.NextID()
|
|
||||||
if err != nil {
|
|
||||||
return nil, serverError("id_generate_failed", err.Error())
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
contact := strings.TrimSpace(req.Contact)
|
|
||||||
leader := bdLeaderRow{
|
|
||||||
ID: leaderID,
|
|
||||||
Origin: sysOrigin,
|
|
||||||
UserID: target.ID,
|
|
||||||
Contact: contact,
|
|
||||||
RegionID: regionID,
|
|
||||||
CreateTime: now,
|
|
||||||
UpdateTime: now,
|
|
||||||
CreateUser: user.UserID,
|
|
||||||
UpdateUser: user.UserID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := tx.Create(&leader).Error; err != nil {
|
|
||||||
return serverError("bd_leader_create_failed", err.Error())
|
|
||||||
}
|
|
||||||
if err := upsertBDBaseInfo(tx, leader, user.UserID, now); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := upsertUserHistoryBD(tx, target.ID, user.UserID, now); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AddBDLeaderResponse{
|
|
||||||
Success: true,
|
|
||||||
Leader: bdLeaderRowToView(leader, target),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) requireManagerInfo(ctx context.Context, user AuthUser) (teamManagerInfoRow, error) {
|
|
||||||
if user.UserID <= 0 {
|
|
||||||
return teamManagerInfoRow{}, unauthorized("invalid_user", "authenticated user is required")
|
|
||||||
}
|
|
||||||
if s.db == nil {
|
|
||||||
return teamManagerInfoRow{}, serviceUnavailable("database_unavailable", "database is unavailable")
|
|
||||||
}
|
|
||||||
|
|
||||||
var row teamManagerInfoRow
|
|
||||||
err := s.db.WithContext(ctx).
|
|
||||||
Table("team_manager_base_info").
|
|
||||||
Where("user_id = ?", user.UserID).
|
|
||||||
First(&row).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return teamManagerInfoRow{}, forbidden("not_manager", "user is not manager")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return teamManagerInfoRow{}, serverError("manager_query_failed", err.Error())
|
|
||||||
}
|
|
||||||
return row, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func managerSysOrigin(manager teamManagerInfoRow, user AuthUser) string {
|
|
||||||
if origin := strings.TrimSpace(manager.Origin); origin != "" {
|
|
||||||
return origin
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(user.SysOrigin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) userIsAdministrator(ctx context.Context, userID int64) (bool, error) {
|
|
||||||
var count int64
|
|
||||||
if err := s.db.WithContext(ctx).
|
|
||||||
Table("sys_administrator").
|
|
||||||
Where("user_id = ? AND status = ?", userID, true).
|
|
||||||
Count(&count).Error; err != nil {
|
|
||||||
return false, serverError("administrator_query_failed", err.Error())
|
|
||||||
}
|
|
||||||
return count > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) userIsBDLeader(ctx context.Context, userID int64) (bool, error) {
|
|
||||||
var count int64
|
|
||||||
if err := s.db.WithContext(ctx).
|
|
||||||
Table("room_bd_lead_base_info").
|
|
||||||
Where("user_id = ?", userID).
|
|
||||||
Count(&count).Error; err != nil {
|
|
||||||
return false, serverError("bd_leader_query_failed", err.Error())
|
|
||||||
}
|
|
||||||
return count > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func upsertBDBaseInfo(tx *gorm.DB, leader bdLeaderRow, operatorID int64, now time.Time) error {
|
|
||||||
var row businessDevelopmentBaseInfoRow
|
|
||||||
err := tx.Where("user_id = ?", leader.UserID).First(&row).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
id, idErr := utils.NextID()
|
|
||||||
if idErr != nil {
|
|
||||||
return serverError("id_generate_failed", idErr.Error())
|
|
||||||
}
|
|
||||||
leadUserID := leader.UserID
|
|
||||||
return tx.Create(&businessDevelopmentBaseInfoRow{
|
|
||||||
ID: id,
|
|
||||||
Origin: leader.Origin,
|
|
||||||
BDLeadUserID: &leadUserID,
|
|
||||||
UserID: leader.UserID,
|
|
||||||
Contact: leader.Contact,
|
|
||||||
Region: leader.RegionID,
|
|
||||||
MemberQuantity: 0,
|
|
||||||
CreateTime: now,
|
|
||||||
UpdateTime: now,
|
|
||||||
CreateUser: operatorID,
|
|
||||||
UpdateUser: operatorID,
|
|
||||||
}).Error
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return serverError("bd_info_query_failed", err.Error())
|
|
||||||
}
|
|
||||||
if row.BDLeadUserID != nil && *row.BDLeadUserID > 0 {
|
|
||||||
return badRequest("user_already_bound_bd_leader", "user already has a BD leader")
|
|
||||||
}
|
|
||||||
|
|
||||||
updates := map[string]any{
|
|
||||||
"bd_lead_user_id": leader.UserID,
|
|
||||||
"region": leader.RegionID,
|
|
||||||
"update_time": now,
|
|
||||||
"update_user": operatorID,
|
|
||||||
}
|
|
||||||
if leader.Contact != "" {
|
|
||||||
updates["contact"] = leader.Contact
|
|
||||||
}
|
|
||||||
if leader.Origin != "" {
|
|
||||||
updates["sys_origin"] = leader.Origin
|
|
||||||
}
|
|
||||||
if err := tx.Model(&businessDevelopmentBaseInfoRow{}).
|
|
||||||
Where("id = ?", row.ID).
|
|
||||||
Updates(updates).Error; err != nil {
|
|
||||||
return serverError("bd_info_update_failed", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func upsertUserHistoryBD(tx *gorm.DB, userID int64, operatorID int64, now time.Time) error {
|
|
||||||
var row userHistoryIdentityRow
|
|
||||||
err := tx.Where("user_id = ?", userID).First(&row).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return tx.Create(&userHistoryIdentityRow{
|
|
||||||
UserID: userID,
|
|
||||||
IsBD: true,
|
|
||||||
CreateTime: now,
|
|
||||||
UpdateTime: now,
|
|
||||||
CreateUser: operatorID,
|
|
||||||
UpdateUser: operatorID,
|
|
||||||
}).Error
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return serverError("history_identity_query_failed", err.Error())
|
|
||||||
}
|
|
||||||
if err := tx.Model(&userHistoryIdentityRow{}).
|
|
||||||
Where("user_id = ?", userID).
|
|
||||||
Updates(map[string]any{
|
|
||||||
"is_bd": true,
|
|
||||||
"update_time": now,
|
|
||||||
"update_user": operatorID,
|
|
||||||
}).Error; err != nil {
|
|
||||||
return serverError("history_identity_update_failed", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bdLeaderRowToView(leader bdLeaderRow, user userBaseInfoRow) BDLeaderView {
|
|
||||||
return BDLeaderView{
|
|
||||||
ID: ID(leader.ID),
|
|
||||||
UserID: ID(leader.UserID),
|
|
||||||
Account: user.Account,
|
|
||||||
UserAvatar: user.UserAvatar,
|
|
||||||
UserNickname: user.UserNickname,
|
|
||||||
Contact: leader.Contact,
|
|
||||||
RegionID: leader.RegionID,
|
|
||||||
OriginSys: leader.Origin,
|
|
||||||
CreateTime: leader.CreateTime,
|
|
||||||
UpdateTime: leader.UpdateTime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func bdLeaderListRowToView(row bdLeaderListRow) BDLeaderView {
|
|
||||||
return BDLeaderView{
|
|
||||||
ID: ID(row.ID),
|
|
||||||
UserID: ID(row.UserID),
|
|
||||||
Account: row.Account,
|
|
||||||
UserAvatar: row.UserAvatar,
|
|
||||||
UserNickname: row.UserNickname,
|
|
||||||
Contact: row.Contact,
|
|
||||||
RegionID: row.RegionID,
|
|
||||||
OriginSys: row.Origin,
|
|
||||||
CreateTime: row.CreateTime,
|
|
||||||
UpdateTime: row.UpdateTime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -14,13 +14,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type teamManagerInfoRow struct {
|
type teamManagerInfoRow struct {
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
ID int64 `gorm:"column:id;primaryKey"`
|
||||||
UserID int64 `gorm:"column:user_id"`
|
UserID int64 `gorm:"column:user_id"`
|
||||||
Origin string `gorm:"column:sys_origin"`
|
Origin string `gorm:"column:sys_origin"`
|
||||||
Contact string `gorm:"column:contact"`
|
|
||||||
RegionID string `gorm:"column:region_id"`
|
|
||||||
CreateUser int64 `gorm:"column:create_user"`
|
|
||||||
UpdateUser int64 `gorm:"column:update_user"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (teamManagerInfoRow) TableName() string {
|
func (teamManagerInfoRow) TableName() string {
|
||||||
@ -328,8 +324,25 @@ func (s *Service) UnbanUser(ctx context.Context, user AuthUser, req UnbanUserReq
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ensureManager(ctx context.Context, user AuthUser) error {
|
func (s *Service) ensureManager(ctx context.Context, user AuthUser) error {
|
||||||
_, err := s.requireManagerInfo(ctx, user)
|
if user.UserID <= 0 {
|
||||||
return err
|
return unauthorized("invalid_user", "authenticated user is required")
|
||||||
|
}
|
||||||
|
if s.db == nil {
|
||||||
|
return serviceUnavailable("database_unavailable", "database is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
err := s.db.WithContext(ctx).
|
||||||
|
Table("team_manager_base_info").
|
||||||
|
Where("user_id = ?", user.UserID).
|
||||||
|
Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return serverError("manager_query_failed", err.Error())
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return forbidden("not_manager", "user is not manager")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) findUserView(ctx context.Context, account string) (UserView, error) {
|
func (s *Service) findUserView(ctx context.Context, account string) (UserView, error) {
|
||||||
|
|||||||
@ -167,73 +167,6 @@ func TestGetProfileToleratesRechargeQueryFailure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddBDLeaderCreatesLeaderAndListEntry(t *testing.T) {
|
|
||||||
service, _ := newManagerCenterTestService(t)
|
|
||||||
db := service.db.(*gorm.DB)
|
|
||||||
seedManager(t, db, 1)
|
|
||||||
seedUsers(t, db,
|
|
||||||
userBaseInfoRow{ID: 2, Account: "2002", UserNickname: "leader", OriginSys: "LIKEI"},
|
|
||||||
)
|
|
||||||
|
|
||||||
resp, err := service.AddBDLeader(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"}, AddBDLeaderRequest{
|
|
||||||
UserID: ID(2),
|
|
||||||
Contact: "whatsapp",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("AddBDLeader() error = %v", err)
|
|
||||||
}
|
|
||||||
if !resp.Success || resp.Leader.UserID.Int64() != 2 || resp.Leader.RegionID != "SA" {
|
|
||||||
t.Fatalf("resp = %+v, want created leader for user 2 in manager region", resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bdInfo businessDevelopmentBaseInfoRow
|
|
||||||
if err := db.Where("user_id = ?", int64(2)).First(&bdInfo).Error; err != nil {
|
|
||||||
t.Fatalf("load bd info: %v", err)
|
|
||||||
}
|
|
||||||
if bdInfo.BDLeadUserID == nil || *bdInfo.BDLeadUserID != 2 || bdInfo.Region != "SA" {
|
|
||||||
t.Fatalf("bd info = %+v, want self-linked BD leader info", bdInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
var identity userHistoryIdentityRow
|
|
||||||
if err := db.Where("user_id = ?", int64(2)).First(&identity).Error; err != nil {
|
|
||||||
t.Fatalf("load identity: %v", err)
|
|
||||||
}
|
|
||||||
if !identity.IsBD {
|
|
||||||
t.Fatalf("identity = %+v, want is_bd=true", identity)
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := service.ListBDLeaders(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ListBDLeaders() error = %v", err)
|
|
||||||
}
|
|
||||||
if list.Count != 1 || list.Records[0].UserID.Int64() != 2 || list.Records[0].Account != "2002" {
|
|
||||||
t.Fatalf("list = %+v, want one created leader", list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddBDLeaderRejectsExistingLeader(t *testing.T) {
|
|
||||||
service, _ := newManagerCenterTestService(t)
|
|
||||||
db := service.db.(*gorm.DB)
|
|
||||||
seedManager(t, db, 1)
|
|
||||||
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2002", OriginSys: "LIKEI"})
|
|
||||||
if err := db.Create(&bdLeaderRow{ID: 10, UserID: 2, Origin: "LIKEI", RegionID: "SA", CreateUser: 1}).Error; err != nil {
|
|
||||||
t.Fatalf("seed bd leader: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := service.AddBDLeader(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"}, AddBDLeaderRequest{UserID: ID(2)})
|
|
||||||
assertAppErrorCode(t, err, "user_already_bd_leader")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddBDLeaderRejectsTargetFromOtherOrigin(t *testing.T) {
|
|
||||||
service, _ := newManagerCenterTestService(t)
|
|
||||||
db := service.db.(*gorm.DB)
|
|
||||||
seedManager(t, db, 1)
|
|
||||||
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2002", OriginSys: "ATYOU"})
|
|
||||||
|
|
||||||
_, err := service.AddBDLeader(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"}, AddBDLeaderRequest{UserID: ID(2)})
|
|
||||||
assertAppErrorCode(t, err, "bd_leader_origin_mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSendPropsRejectsNonAdminFree(t *testing.T) {
|
func TestSendPropsRejectsNonAdminFree(t *testing.T) {
|
||||||
service, fake := newManagerCenterTestService(t)
|
service, fake := newManagerCenterTestService(t)
|
||||||
db := service.db.(*gorm.DB)
|
db := service.db.(*gorm.DB)
|
||||||
@ -547,10 +480,6 @@ func newManagerCenterTestService(t *testing.T) (*Service, *fakeManagerGateway) {
|
|||||||
if err := db.AutoMigrate(
|
if err := db.AutoMigrate(
|
||||||
&teamManagerInfoRow{},
|
&teamManagerInfoRow{},
|
||||||
&userBaseInfoRow{},
|
&userBaseInfoRow{},
|
||||||
&bdLeaderRow{},
|
|
||||||
&businessDevelopmentBaseInfoRow{},
|
|
||||||
&userHistoryIdentityRow{},
|
|
||||||
&administratorRow{},
|
|
||||||
&propsSourceRecordRow{},
|
&propsSourceRecordRow{},
|
||||||
&propsNobleVIPAbilityRow{},
|
&propsNobleVIPAbilityRow{},
|
||||||
&model.VipLevelConfig{},
|
&model.VipLevelConfig{},
|
||||||
@ -569,7 +498,7 @@ func newManagerCenterTestService(t *testing.T) (*Service, *fakeManagerGateway) {
|
|||||||
|
|
||||||
func seedManager(t *testing.T, db *gorm.DB, userID int64) {
|
func seedManager(t *testing.T, db *gorm.DB, userID int64) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err := db.Create(&teamManagerInfoRow{ID: userID, UserID: userID, Origin: "LIKEI", RegionID: "SA"}).Error; err != nil {
|
if err := db.Create(&teamManagerInfoRow{ID: userID, UserID: userID, Origin: "LIKEI"}).Error; err != nil {
|
||||||
t.Fatalf("seed manager: %v", err)
|
t.Fatalf("seed manager: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/common"
|
"chatapp3-golang/internal/common"
|
||||||
"chatapp3-golang/internal/integration"
|
"chatapp3-golang/internal/integration"
|
||||||
@ -179,35 +178,6 @@ type UnbanUserResponse struct {
|
|||||||
AccountStatus string `json:"accountStatus"`
|
AccountStatus string `json:"accountStatus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BDLeaderListResponse struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
Records []BDLeaderView `json:"records"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BDLeaderView struct {
|
|
||||||
ID ID `json:"id"`
|
|
||||||
UserID ID `json:"userId"`
|
|
||||||
Account string `json:"account"`
|
|
||||||
UserAvatar string `json:"userAvatar"`
|
|
||||||
UserNickname string `json:"userNickname"`
|
|
||||||
Contact string `json:"contact,omitempty"`
|
|
||||||
RegionID string `json:"regionId,omitempty"`
|
|
||||||
OriginSys string `json:"originSys,omitempty"`
|
|
||||||
CreateTime time.Time `json:"createTime,omitempty"`
|
|
||||||
UpdateTime time.Time `json:"updateTime,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddBDLeaderRequest struct {
|
|
||||||
UserID ID `json:"userId"`
|
|
||||||
Contact string `json:"contact"`
|
|
||||||
RegionID string `json:"regionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddBDLeaderResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Leader BDLeaderView `json:"leader"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func badRequest(code, message string) error {
|
func badRequest(code, message string) error {
|
||||||
return NewAppError(http.StatusBadRequest, code, message)
|
return NewAppError(http.StatusBadRequest, code, message)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,8 +66,6 @@ func (s *Service) SendRegionBroadcast(ctx context.Context, req RegionBroadcastRe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshHighWinMessage(data, messageType)
|
|
||||||
refreshCPRelationMessage(data, messageType)
|
|
||||||
|
|
||||||
body := map[string]any{
|
body := map[string]any{
|
||||||
"type": messageType,
|
"type": messageType,
|
||||||
@ -184,60 +182,6 @@ func firstInt64Value(values ...any) int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshHighWinMessage(data map[string]any, messageType string) {
|
|
||||||
target := highWinPlayTarget(messageType)
|
|
||||||
if target == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user := firstTextValue(data["userNickname"], data["nickname"], data["account"], data["actualAccount"], data["userId"])
|
|
||||||
multiple := firstTextValue(data["multiple"], data["winMultiple"])
|
|
||||||
amount := firstTextValue(data["winAmount"], data["awardAmount"])
|
|
||||||
if user == "" || multiple == "" || amount == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data["msg"] = fmt.Sprintf("%s play %s get x %s win %s coins!!!", user, target, multiple, amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func highWinPlayTarget(messageType string) string {
|
|
||||||
switch strings.TrimSpace(messageType) {
|
|
||||||
case "GAME_LUCKY_GIFT":
|
|
||||||
return "lucky_gift"
|
|
||||||
case "GAME_BAISHUN_WIN":
|
|
||||||
return "game"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshCPRelationMessage(data map[string]any, messageType string) {
|
|
||||||
if strings.TrimSpace(messageType) != "CP_RELATION_BROADCAST" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user := firstTextValue(data["userNickname"], data["nickname"], data["account"], data["actualAccount"], data["userId"])
|
|
||||||
cpUser := firstTextValue(data["cpUserNickname"], data["acceptNickname"], data["toUserName"], data["cpUserAccount"], data["acceptAccount"], data["cpUserId"], data["acceptUserId"])
|
|
||||||
if user == "" || cpUser == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch strings.ToUpper(strings.TrimSpace(firstTextValue(data["relationType"]))) {
|
|
||||||
case "BROTHER", "BROTHERS":
|
|
||||||
data["msg"] = fmt.Sprintf("%s与%s结为兄弟", user, cpUser)
|
|
||||||
case "SISTERS", "SISTER":
|
|
||||||
data["msg"] = fmt.Sprintf("%s与%s结为姐妹", user, cpUser)
|
|
||||||
default:
|
|
||||||
data["msg"] = fmt.Sprintf("%s与%s get cp relation", user, cpUser)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func firstTextValue(values ...any) string {
|
|
||||||
for _, value := range values {
|
|
||||||
text := strings.TrimSpace(fmt.Sprint(value))
|
|
||||||
if text != "" && text != "<nil>" {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func firstInt64FromList(value any) int64 {
|
func firstInt64FromList(value any) int64 {
|
||||||
switch typed := value.(type) {
|
switch typed := value.(type) {
|
||||||
case []any:
|
case []any:
|
||||||
|
|||||||
@ -133,97 +133,6 @@ func TestSendRegionBroadcastResolvesRegionAndEnrichesGiftPayload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendRegionBroadcastRefreshesHighWinMessageWithProfile(t *testing.T) {
|
|
||||||
service, db := newRegionIMGroupTestService(t)
|
|
||||||
fakeIM := &fakeIMGateway{}
|
|
||||||
service.im = fakeIM
|
|
||||||
now := time.Now()
|
|
||||||
if err := db.Create(&model.VoiceRoomRegionIMGroup{
|
|
||||||
ID: 3010,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
RegionCode: "AR",
|
|
||||||
RegionName: "Arab",
|
|
||||||
GroupID: "@region-ar",
|
|
||||||
GroupName: "region-ar",
|
|
||||||
Status: imGroupStatusActive,
|
|
||||||
CreateTime: now,
|
|
||||||
UpdateTime: now,
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("seed im group: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := service.SendRegionBroadcast(context.Background(), RegionBroadcastRequest{
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
Type: "GAME_BAISHUN_WIN",
|
|
||||||
Data: map[string]any{
|
|
||||||
"userId": "1001",
|
|
||||||
"roomId": "9001",
|
|
||||||
"multiple": 10,
|
|
||||||
"winAmount": 1000,
|
|
||||||
"msg": "1001 play game get x 10 win 1000 coins!!!",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SendRegionBroadcast() error = %v", err)
|
|
||||||
}
|
|
||||||
body, ok := fakeIM.sent[0].body.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("sent body type = %T", fakeIM.sent[0].body)
|
|
||||||
}
|
|
||||||
data, ok := body["data"].(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("sent data type = %T", body["data"])
|
|
||||||
}
|
|
||||||
if data["msg"] != "sender play game get x 10 win 1000 coins!!!" {
|
|
||||||
t.Fatalf("msg = %v", data["msg"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSendRegionBroadcastRefreshesCPRelationMessageWithProfiles(t *testing.T) {
|
|
||||||
service, db := newRegionIMGroupTestService(t)
|
|
||||||
fakeIM := &fakeIMGateway{}
|
|
||||||
service.im = fakeIM
|
|
||||||
now := time.Now()
|
|
||||||
if err := db.Create(&model.VoiceRoomRegionIMGroup{
|
|
||||||
ID: 3011,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
RegionCode: "AR",
|
|
||||||
RegionName: "Arab",
|
|
||||||
GroupID: "@region-ar",
|
|
||||||
GroupName: "region-ar",
|
|
||||||
Status: imGroupStatusActive,
|
|
||||||
CreateTime: now,
|
|
||||||
UpdateTime: now,
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("seed im group: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := service.SendRegionBroadcast(context.Background(), RegionBroadcastRequest{
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
Type: "CP_RELATION_BROADCAST",
|
|
||||||
Data: map[string]any{
|
|
||||||
"userId": "1001",
|
|
||||||
"acceptUserId": "1002",
|
|
||||||
"relationType": "SISTERS",
|
|
||||||
"msg": "1001与1002结为姐妹",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SendRegionBroadcast() error = %v", err)
|
|
||||||
}
|
|
||||||
body, ok := fakeIM.sent[0].body.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("sent body type = %T", fakeIM.sent[0].body)
|
|
||||||
}
|
|
||||||
data, ok := body["data"].(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("sent data type = %T", body["data"])
|
|
||||||
}
|
|
||||||
if data["msg"] != "sender与claimer结为姐妹" {
|
|
||||||
t.Fatalf("msg = %v", data["msg"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSendRegionBroadcastPrefersJavaRegionResolver(t *testing.T) {
|
func TestSendRegionBroadcastPrefersJavaRegionResolver(t *testing.T) {
|
||||||
service, db := newRegionIMGroupTestService(t)
|
service, db := newRegionIMGroupTestService(t)
|
||||||
service.regionResolver = fakeRegionResolver{
|
service.regionResolver = fakeRegionResolver{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user