Diff Coverage

Diff: origin/master..HEAD, staged and unstaged changes

Source File Diff Coverage (%) Missing Lines
xmod_resource_service/src/application/announcement/cmd_service/push_version_update_as.go 50.0% 20-21,26-27
xmod_resource_service/src/domain/announcement/agg_version_update.go 79.5% 32,39,47,50,53,56,59,129
xmod_resource_service/src/domain/announcement/factory.go 77.3% 145,151,157,163,175
xmod_resource_service/src/domain/announcement/model/announcement.go 100%  
xmod_resource_service/src/domain/announcement/model/const.go 100%  
xmod_resource_service/src/domain/service/announcement/game_change.go 55.6% 71-72,75-76
xmod_resource_service/src/infrastructure/gateway_impl/announcement/announcement.go 58.5% 837,845,848,888,891,1002,1010-1012,1014,1022-1024,1026,1170,1189,1198-1199,1209-1210,1226-1227,1250-1251,1263-1264,1269
xmod_resource_service/src/interfaces/http/controllers/announcement_views.go 61.5% 184-185,191-193
xmod_resource_service/src/interfaces/http/routers/announcement_gen_router.go 100%  
xmod_resource_service/src/application/announcement/cmd_service/push_version_update_as.go
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
	c *core.Context,
	params *proto.PushVersionUpdateAnnouncementParams,
) (result *proto.PushVersionUpdateAnnouncementResult, err error) {
	if params.GameId <= 0 || params.VersionId <= 0 {
		err = fmt.Errorf("game_id and version_id are required")
		return
	}

	errD := service.PushGameUpdate(c, params.GameId, params.VersionId)
	if errD != nil {
		if e := errD.ToError(); e != nil {
			return nil, e
		}
	}

	return &proto.PushVersionUpdateAnnouncementResult{}, nil
xmod_resource_service/src/domain/announcement/agg_version_update.go
28
29
30
31
32
33
34
35
36

// Push 执行推送:生成推送计划 -> 调用 Gateway 写库(由 infrastructure 层事务保证)
func (a *VersionUpdateAgg) Push(c *core.Context) *goToolError.DomainError {
	if err := a.validate(); err != nil {
		return err
	}

	now := time.Now()
	toCreate, toUpdate := a.buildPushPlan(now)
35
36
37
38
39
40
41
42
	now := time.Now()
	toCreate, toUpdate := a.buildPushPlan(now)

	if err := gateway.NewAnnouncementGateway().PushVersionUpdateAnnouncement(c, toCreate, toUpdate); err != nil {
		return goToolError.NewError(domainError.ErrPushVersionUpdateAnnouncement, map[string]string{"error": err.Error()})
	}
	return nil
}
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

// validate 校验聚合数据是否合法
func (a *VersionUpdateAgg) validate() *goToolError.DomainError {
	if a.gameId <= 0 {
		return goToolError.NewError(domainError.ErrInvalidParams, map[string]string{"reason": "game_id is required"})
	}
	if a.versionId <= 0 {
		return goToolError.NewError(domainError.ErrInvalidParams, map[string]string{"reason": "version_id is required"})
	}
	if a.versionName == "" {
		return goToolError.NewError(domainError.ErrInvalidParams, map[string]string{"reason": "version_name is empty"})
	}
	if a.cheatsNum <= 0 {
		return goToolError.NewError(domainError.ErrInvalidParams, map[string]string{"reason": "cheats_num must be > 0"})
	}
	if a.platformName == "" {
		return goToolError.NewError(domainError.ErrInvalidParams, map[string]string{"reason": "platform_name is empty"})
	}
	return nil
}
125
126
127
128
129
130
131
132
133
func lookupTemplate(templates map[string]string, langFlag string) string {
	if tmpl, ok := templates[langFlag]; ok {
		return tmpl
	}
	return templates["en-us"]
}

func buildVersionUpdateTitle(langFlag, gameName string, cheatsNum int64, platformName string) string {
	return fmt.Sprintf(lookupTemplate(modelAnnouncement.LangTitleUpdateTemplate, langFlag), gameName, cheatsNum, platformName)
xmod_resource_service/src/domain/announcement/factory.go
141
142
143
144
145
146
147
148
149

	// 1. 查询版本信息(获取版本号、修改项数量、所属平台ID)
	version, errDb = repoVersion.FindById(c, versionId)
	if errDb != nil {
		return nil, goToolError.NewError(domainError.ErrGetGameVersion, map[string]string{"error": errDb.Error()})
	}

	// 2. 查询游戏信息(获取英文名、中文名)
	game, errDb = repoGame.FindById(c, gameId)
147
148
149
150
151
152
153
154
155

	// 2. 查询游戏信息(获取英文名、中文名)
	game, errDb = repoGame.FindById(c, gameId)
	if errDb != nil {
		return nil, goToolError.NewError(domainError.ErrGameFindById, map[string]string{"error": errDb.Error()})
	}

	// 3. 查询平台信息(获取平台名称)
	platform, errDb = repoPlatform.FindById(c, version.GamePlatformID)
153
154
155
156
157
158
159
160
161

	// 3. 查询平台信息(获取平台名称)
	platform, errDb = repoPlatform.FindById(c, version.GamePlatformID)
	if errDb != nil {
		return nil, goToolError.NewError(domainError.ErrPlatformFindById, map[string]string{"error": errDb.Error()})
	}

	// 4. 查询当前 Tracker 状态
	trackers, errDb = repoAnn.GetGameUpdateTrackers(c, gameId)
159
160
161
162
163
164
165
166
167

	// 4. 查询当前 Tracker 状态
	trackers, errDb = repoAnn.GetGameUpdateTrackers(c, gameId)
	if errDb != nil {
		return nil, goToolError.NewError(domainError.ErrGetAnnouncement, map[string]string{"error": errDb.Error()})
	}

	// 5. 解析游戏中文名
	gameNameZhCn, _ := game.ParseTitleTranslates()
171
172
173
174
175
176
177
178
179
	}

	numOfCheats, errDb = repoCheats.Count(c, versionId)
	if errDb != nil {
		return nil, goToolError.NewError(domainError.ErrGetGameVersion, map[string]string{"error": errDb.Error()})
	}

	agg = &VersionUpdateAgg{
		gameId:       gameId,
xmod_resource_service/src/domain/service/announcement/game_change.go
67
68
69
70
71
72
73
74
75
76
77
78
79
80
	)

	agg, err = domain.FactoryVersionUpdateAgg(c, gameId, versionId)
	if err != nil {
		logger.Log.Error(c, "FactoryVersionUpdateAgg err:%+v", err)
		return
	}
	if err = agg.Push(c); err != nil {
		logger.Log.Error(c, "agg.Push err:%+v", err)
		return
	}

	return nil
}
xmod_resource_service/src/infrastructure/gateway_impl/announcement/announcement.go
833
834
835
836
837
838
839
840
841
	// 构造公告数据列表
	var announcements []*modelAnnouncement.AnnouncementDataModel

	// 遍历所有语言代码
	for _, languageFlag := range modelAnnouncement.AllLanguageFlagList() {
		// 根据语言选择游戏名称
		gameName := gameNameEn
		if languageFlag == "zh-cn" {
			gameName = gameNameZhCn
841
842
843
844
845
846
847
848
849
850
851
852
			gameName = gameNameZhCn
		}

		// 获取对应语言的通知模板
		contentTemplate, exists := modelAnnouncement.LangContentLiveTemplate[languageFlag]
		if !exists {
			logger.Log.Warnf("No template found for language: %s, using English template", languageFlag)
			contentTemplate = modelAnnouncement.LangContentLiveTemplate["en-us"]
		}

		// 生成公告内容
		content := fmt.Sprintf(contentTemplate, gameName)
884
885
886
887
888
889
890
891
892
893
894
895
			coverUrl = fmt.Sprintf("https://xmod-static.xmodhub.cn/images/%d.webp", gameId)
		}

		// 生成公告标题
		titleTemplate, titleExists := modelAnnouncement.LangTitleLiveTemplate[languageFlag]
		if !titleExists {
			logger.Log.Warnf("No title template found for language: %s, using English template", languageFlag)
			titleTemplate = modelAnnouncement.LangTitleLiveTemplate["en-us"]
		}
		noticeTitle := fmt.Sprintf(titleTemplate, gameName)

		// 创建公告数据模型
 998
 999
1000
1001
1002
1003
1004
1005
1006
	// 构造公告数据列表
	var announcements []*modelAnnouncement.AnnouncementDataModel

	// 遍历所有语言代码
	for _, languageFlag := range modelAnnouncement.AllLanguageFlagList() {
		// 根据语言选择游戏名称
		gameName := gameNameEn
		if languageFlag == "zh-cn" {
			gameName = gameNameZhCn
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
			gameName = gameNameZhCn
		}

		// 获取对应语言的通知模板(使用 domain 层统一定义,缺失时回退 en-us)
		contentTmpl, ok := modelAnnouncement.LangContentUpdateTemplate[languageFlag]
		if !ok {
			contentTmpl = modelAnnouncement.LangContentUpdateTemplate["en-us"]
		}
		content := strings.ReplaceAll(contentTmpl, "{0}", gameName)
		content = strings.ReplaceAll(content, "{1}", versionName)
		content = strings.ReplaceAll(content, "{2}", platformName)

		// 生成游戏封面URL
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
		// 生成游戏封面URL
		coverUrl := fmt.Sprintf("https://xmod-static.xmodhub.cn/images/%d.webp", gameId)

		// 生成公告标题(使用 domain 层统一定义,缺失时回退 en-us)
		titleTmpl, ok := modelAnnouncement.LangTitleUpdateTemplate[languageFlag]
		if !ok {
			titleTmpl = modelAnnouncement.LangTitleUpdateTemplate["en-us"]
		}
		updateTitle := fmt.Sprintf(titleTmpl, gameName, cheatsNum, platformName)

		// 创建公告数据模型
		announcement := &modelAnnouncement.AnnouncementDataModel{
			NoticeTitle:         updateTitle,
1166
1167
1168
1169
1170
1171
1172
1173
1174
	var list []*modelAnnouncement.GameUpdateTracker
	if err = a.db.WithContext(c).
		Where("game_id = ?", gameId).
		Find(&list).Error; err != nil {
		return
	}
	trackers = make(map[string]*modelAnnouncement.GameUpdateTracker, len(list))
	for _, t := range list {
		trackers[t.LanguageFlag] = t
1185
1186
1187
1188
1189
1190
1191
1192
1193
	now := time.Now()
	tx := a.db.WithContext(c).Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	// ── 1. 批量创建新公告 ─────────────────────────────────────────
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
	for _, item := range toCreate {
		item.Announcement.CreatedAt = now
		item.Announcement.UpdatedAt = now
		if err := tx.Create(item.Announcement).Error; err != nil {
			tx.Rollback()
			return fmt.Errorf("create announcement error: %w", err)
		}
		relation := &modelAnnouncement.AnnouncementRelation{
			TargetType:     1,
			TargetID:       item.GameId,
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
			CreatedAt:      now,
			UpdatedAt:      now,
		}
		if err := tx.Create(relation).Error; err != nil {
			tx.Rollback()
			return fmt.Errorf("create relation error: %w", err)
		}
	}

	// ── 2. 批量更新已有公告内容 ──────────────────────────────────
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
				"end_time":             target.EndTime,
				"remarks":              target.Remarks,
				"updated_at":           target.UpdatedAt,
			}).Error; err != nil {
			tx.Rollback()
			return fmt.Errorf("update announcement error: %w", err)
		}
	}

	// ── 3. Upsert Tracker(处理并发安全)────────────────────────
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
				"announcement_id", "latest_version_id", "latest_version_name",
				"push_count", "updated_at",
			}),
		}).Create(tracker).Error; err != nil {
			tx.Rollback()
			return fmt.Errorf("upsert tracker error: %w", err)
		}
	}
	for _, target := range toUpdate {
		if err := tx.Model(&modelAnnouncement.GameUpdateTracker{}).
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
				"latest_version_name": target.TrackerLatestVersionName,
				"push_count":          gorm.Expr("push_count + 1"),
				"updated_at":          target.UpdatedAt,
			}).Error; err != nil {
			tx.Rollback()
			return fmt.Errorf("update tracker error: %w", err)
		}
	}

	if err := tx.Commit().Error; err != nil {
		return fmt.Errorf("commit error: %w", err)
	}

	//// ── 4. MQTT 推送(仅发一条通知,避免消息风暴)──────────
	//// 优先取新建的公告,否则取更新的公告,只发一条 MQTT 通知客户端刷新
xmod_resource_service/src/interfaces/http/controllers/announcement_views.go
180
181
182
183
184
185
186
187
188
189
			BodyParam: params,
		},
	)
	if e != nil {
		c.JSON(e.Code, e.Data)
		return
	}

	as := cmd_service.PushVersionUpdateAnnouncementAS{}
	result, err := as.PushVersionUpdateAnnouncement(core.FromContext(c), params)
187
188
189
190
191
192
193
194
195
196
197

	as := cmd_service.PushVersionUpdateAnnouncementAS{}
	result, err := as.PushVersionUpdateAnnouncement(core.FromContext(c), params)
	if err != nil {
		res := &commonProto.CommonErrorResp{ErrorCode: "400", ErrorMsg: err.Error()}
		c.JSON(400, res)
		return
	}

	c.JSON(http.StatusOK, result)
	return