chapter.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. package service
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "gadmin/config"
  6. "gadmin/internal/admin/consts"
  7. "gadmin/internal/admin/forms"
  8. "gadmin/internal/elastic/eapi"
  9. "gadmin/internal/gorm/model"
  10. "gadmin/internal/gorm/query"
  11. "gadmin/package/gmdata"
  12. "gadmin/utility"
  13. "gadmin/utility/player"
  14. "gadmin/utility/serializer"
  15. "math"
  16. "os"
  17. "sort"
  18. "strings"
  19. "sync"
  20. "github.com/gin-gonic/gin"
  21. "github.com/sirupsen/logrus"
  22. )
  23. // Chapter 章节服务
  24. var Chapter = new(sChapter)
  25. type sChapter struct{}
  26. func (s *sChapter) OrdinaryList(ctx *gin.Context, req forms.ChapterOrdinaryListReq) (resp serializer.Response) {
  27. type passData struct {
  28. Id int32 `json:"id"`
  29. Name string `json:"name"`
  30. RoomCount int32 `json:"roomCount"`
  31. Count int64 `json:"count"`
  32. Difficulty int64 `json:"difficulty"`
  33. Pass bool `json:"pass"`
  34. IsActivity bool `json:"isActivity"`
  35. }
  36. DB, err := player.GetDBByUserId(req.PlayerId)
  37. if err != nil {
  38. return serializer.Err(consts.CodeParamErr, "GetDBByUserId err", err)
  39. }
  40. var (
  41. p = query.Use(config.GDBGroup[DB]).PlayerMaterial
  42. passDatas []passData
  43. models forms.ListRes
  44. )
  45. material, err := p.WithContext(ctx).Where(p.Playerid.Eq(req.PlayerId)).First()
  46. if err != nil {
  47. return serializer.Err(consts.CodeParamErr, "查询出错 Find", err)
  48. }
  49. if material == nil || material.ID == 0 || material.PassRoom == "null" {
  50. return serializer.Suc(models)
  51. }
  52. if err := json.Unmarshal([]byte(material.PassRoom), &passDatas); err != nil {
  53. return serializer.Err(consts.CodeParamErr, "Unmarshal err", err)
  54. }
  55. tmp := make([]passData, 0)
  56. for k, v := range passDatas {
  57. if v.Id == 0 || v.Id > 99 {
  58. continue
  59. }
  60. data := gmdata.GetChapterById(v.Id)
  61. if data == nil {
  62. data = &gmdata.Chapter{
  63. Name: `未知`,
  64. }
  65. }
  66. passDatas[k].Name = data.Name
  67. passDatas[k].RoomCount = data.RoomCount
  68. tmp = append(tmp, passDatas[k])
  69. }
  70. // 使用自定义的 Less 函数来指定排序规则
  71. sort.SliceStable(tmp, func(i, j int) bool {
  72. if tmp[i].Id != tmp[j].Id {
  73. return tmp[i].Id < tmp[j].Id // 根据 Id 升序排列
  74. }
  75. return tmp[i].Difficulty < tmp[j].Difficulty // 如果 Id 相同,则根据 Difficulty 升序排列
  76. })
  77. models.List = tmp
  78. return serializer.Suc(models)
  79. }
  80. func (s *sChapter) ActivityList(ctx *gin.Context, req forms.ChapterActivityListReq) (resp serializer.Response) {
  81. type passData struct {
  82. Id int32 `json:"id"`
  83. Difficulty int32 `json:"difficulty"`
  84. DifficultyName string `json:"difficultyName"`
  85. Name string `json:"name"`
  86. Degree int32 `json:"degree"`
  87. RewardMulti float64 `json:"rewardMulti"`
  88. RewardEffectTime int64 `json:"rewardEffectTime"`
  89. ChallengeCount int64 `json:"challenge_count"`
  90. ClearanceCount int64 `json:"clearance_count"`
  91. FailCount int64 `json:"fail_count"`
  92. }
  93. DB, err := player.GetDBByUserId(req.PlayerId)
  94. if err != nil {
  95. return serializer.Err(consts.CodeParamErr, "GetDBByUserId err", err)
  96. }
  97. var (
  98. c = query.Use(config.DB).Chapter
  99. passDatas []passData
  100. models forms.ListRes
  101. z = query.Use(config.GDBGroup[DB]).ZoneActivity
  102. )
  103. chapters, err := c.WithContext(ctx).Where(c.PlayerID.Eq(req.PlayerId)).Find()
  104. if err != nil {
  105. return serializer.Err(consts.CodeParamErr, "查询出错 Find", err)
  106. }
  107. if len(chapters) == 0 {
  108. return serializer.Suc(models)
  109. }
  110. zone, err := z.WithContext(ctx).Where(z.Playerid.Eq(req.PlayerId)).First()
  111. if err != nil {
  112. return serializer.Err(consts.CodeParamErr, "查询出错 Find", err)
  113. }
  114. // BonusProperties 加成属性
  115. type BonusProperties struct {
  116. DifScale int `json:"difScale"` // 难度系数
  117. RewardMulti float64 `json:"rewardMulti"` // 奖励倍数
  118. RewardEffectTime int64 `json:"rewardEffectTime"` // 奖励倍数生效时间
  119. }
  120. bonusProperties := make(map[string]*BonusProperties)
  121. //difficulty := make(map[string]int32)
  122. if zone != nil {
  123. if err = json.Unmarshal([]byte(zone.BonusProperties), &bonusProperties); err != nil {
  124. return serializer.Err(consts.CodeParamErr, "Unmarshal err", err)
  125. }
  126. }
  127. logrus.Warnf("bonusProperties:%+v", bonusProperties)
  128. //chapterIds := []int64{100, 101, 102, 103}
  129. //var difficultys = []int32{0, 1, 2}
  130. for _, v := range chapters {
  131. var data = gmdata.GetChapterById(v.ChapterID)
  132. if data == nil {
  133. data = &gmdata.Chapter{
  134. Name: `未知`,
  135. }
  136. }
  137. //var ds = []int32{0, 1, 2}
  138. //if v.ChapterID == 103 {
  139. // ds = []int32{0}
  140. //}
  141. //
  142. //for _, difficulty := range ds {
  143. //
  144. //
  145. //}
  146. bs, ok := bonusProperties[fmt.Sprintf("%v_%v", v.ChapterID, v.Difficulty)]
  147. if ok {
  148. passDatas = append(passDatas, passData{
  149. Id: v.ChapterID,
  150. Difficulty: v.Difficulty,
  151. DifficultyName: gmdata.GetDifficultName(int64(v.Difficulty)),
  152. Name: data.Name,
  153. Degree: int32(bs.DifScale),
  154. RewardMulti: bs.RewardMulti,
  155. RewardEffectTime: bs.RewardEffectTime,
  156. ChallengeCount: int64(v.ClearanceCount + v.FailCount),
  157. ClearanceCount: int64(v.ClearanceCount),
  158. FailCount: int64(v.FailCount),
  159. })
  160. } else {
  161. // 没有数据视为没有参与过,直接不显示
  162. //passDatas = append(passDatas, passData{
  163. // Id: v.ChapterID,
  164. // Difficulty: difficulty,
  165. // Name: data.Name,
  166. // Degree: 100, // 这里写死了100,其实有的初始值并不是100,需要知道
  167. // RewardMulti: 1,
  168. // RewardEffectTime: 0,
  169. // ChallengeCount: int64(v.ClearanceCount + v.FailCount),
  170. // ClearanceCount: int64(v.ClearanceCount),
  171. // FailCount: int64(v.FailCount),
  172. //})
  173. }
  174. }
  175. // 远征
  176. var (
  177. cf = query.Use(config.GDBGroup[DB]).Climbfloor
  178. )
  179. cfd, _ := cf.WithContext(ctx).Where(cf.Playerid.Eq(req.PlayerId)).First()
  180. //if err != nil {
  181. // return serializer.Err(consts.CodeParamErr, "查询出错 Climbfloor Find", err)
  182. //}
  183. if cfd == nil {
  184. passDatas = append(passDatas, passData{
  185. Id: 105,
  186. Name: "远征",
  187. Degree: 0,
  188. DifficultyName: gmdata.GetDifficultName(int64(0)),
  189. //ChallengeCount: int64(v.ClearanceCount + v.FailCount),
  190. //ClearanceCount: int64(v.ClearanceCount),
  191. //FailCount: int64(v.FailCount),
  192. })
  193. } else {
  194. passDatas = append(passDatas, passData{
  195. Id: 105,
  196. Name: "远征",
  197. Degree: cfd.MaxFloor,
  198. DifficultyName: gmdata.GetDifficultName(int64(0)),
  199. //ChallengeCount: int64(v.ClearanceCount + v.FailCount),
  200. //ClearanceCount: int64(v.ClearanceCount),
  201. //FailCount: int64(v.FailCount),
  202. })
  203. }
  204. models.List = passDatas
  205. return serializer.Suc(models)
  206. }
  207. func (s *sChapter) doReconnectFromDB(ctx *gin.Context, params forms.ChapterReconnectReq, chapterId, difficulty int32, isNew bool) (count, userCount int64, err error) {
  208. type Result struct {
  209. TotalCount int64 `gorm:"total_count"`
  210. UserCount int64 `gorm:"user_count"`
  211. }
  212. begin, end, _ := utility.GetBeginAndEndOfDay2(params.Day, params.EndDay)
  213. end = end + 1
  214. table := model.GetChapterTable2(chapterId)
  215. var (
  216. q = query.Use(config.DB).ChapterLog.Table(table)
  217. db = q.UnderlyingDB()
  218. )
  219. db = db.Where("event_id = 2 and extra like '%0%' and difficulty = ? and event_at >= ? and event_at <= ?", difficulty, begin, end)
  220. if params.ServerId > 0 {
  221. db = db.Where("server_id = ?", params.ServerId)
  222. }
  223. switch params.ChannelId {
  224. case consts.ChannelIdNone:
  225. // 不选择渠道
  226. case consts.ChannelIdAllAdv, consts.ChannelIdAllWx, consts.ChannelIdAllTT:
  227. // 所有广告渠道
  228. db = db.Where("channel_id in (?)", Channel.GetIdsByType(params.ChannelId))
  229. default:
  230. // 选择指定渠道
  231. db = db.Where("channel_id = ?", params.ChannelId)
  232. }
  233. res := make([]*Result, 0)
  234. if isNew {
  235. err = db.Select("count(*) AS total_count, count( DISTINCT user_id ) AS user_count, DATE (FROM_UNIXTIME( user_created_at )) AS d").Where("user_created_at >= ? and user_created_at <= ?", begin, end).Group("d").Find(&res).Error
  236. } else {
  237. err = db.Select("count(*) AS total_count, count( DISTINCT user_id ) AS user_count").Find(&res).Error
  238. }
  239. if err != nil {
  240. logrus.Errorf("查询出错:%+v", err)
  241. if strings.Contains(err.Error(), "1146") {
  242. createSql := "CREATE TABLE IF NOT EXISTS `%s` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`channel_id` varchar(128) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '渠道ID',`flag` int DEFAULT '0',`user_id` bigint unsigned NOT NULL COMMENT '用户id',`server_id` int NOT NULL DEFAULT '1' COMMENT '服务器ID',`event_id` tinyint unsigned NOT NULL COMMENT '埋点id',`chapter_id` smallint unsigned NOT NULL COMMENT '关卡id',`difficulty` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '困难度',`room_id` bigint NOT NULL COMMENT '房间id',`user_created_at` int unsigned NOT NULL COMMENT '用户注册时间',`event_at` int unsigned NOT NULL COMMENT '埋点事件触发的时间',`event_at_ns` bigint unsigned NOT NULL,`token` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登陆的token',`extra` text COLLATE utf8mb4_general_ci COMMENT '其他参数',PRIMARY KEY (`id`),UNIQUE KEY `user_event_ns` (`user_id`,`event_at_ns`),KEY `created_chapter_event_event_at_idx` (`user_created_at`,`chapter_id`,`event_id`,`event_at`),KEY `event_id_event_at_idx` (`event_id`,`event_at`),KEY `server_id` (`server_id`),KEY `channel_id` (`channel_id`),KEY `idx_event_id` (`event_id`) USING BTREE,KEY `server_id_difficulty_user_id_event_id_extra_index` (`server_id`,`difficulty`,`user_id`,`event_id`),KEY `chapter_logs_100_event_at_index` (`event_at`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关卡信息埋点日志';"
  243. if err := q.UnderlyingDB().Exec(fmt.Sprintf(createSql, table)).Error; err != nil {
  244. logrus.Errorf("创建表%s错误:%+v", table, err)
  245. }
  246. }
  247. return 0, 0, err
  248. }
  249. for _, v := range res {
  250. count += v.TotalCount
  251. userCount += v.UserCount
  252. }
  253. return
  254. }
  255. func (s *sChapter) doReconnect(ctx *gin.Context, params forms.ChapterReconnectReq, chapterId, difficulty int32, isNew bool) (count, userCount int64, err error) {
  256. begin, end, _ := utility.GetBeginAndEndOfDay2(params.Day, params.EndDay)
  257. end = end + 1
  258. index := fmt.Sprintf("%s%s", os.Getenv("ELASTIC_TOPIC"), model.GetChapterTable2(chapterId))
  259. var (
  260. mustNot []eapi.M
  261. where eapi.M
  262. )
  263. must := []eapi.M{
  264. {
  265. "term": eapi.M{
  266. "event_id": "2",
  267. },
  268. },
  269. {
  270. "match": eapi.M{
  271. "extra": "0",
  272. },
  273. },
  274. {
  275. "term": eapi.M{
  276. "chapter_id": chapterId,
  277. },
  278. },
  279. {
  280. "term": eapi.M{
  281. "difficulty": difficulty,
  282. },
  283. },
  284. {
  285. "range": eapi.M{
  286. "event_at": eapi.M{
  287. "gte": begin,
  288. "lte": end,
  289. },
  290. },
  291. },
  292. }
  293. if params.ServerId > 0 {
  294. must = append(must, eapi.M{
  295. "term": eapi.M{
  296. "server_id": params.ServerId,
  297. },
  298. })
  299. }
  300. switch params.ChannelId {
  301. case consts.ChannelIdNone:
  302. // 不选择渠道
  303. case consts.ChannelIdAllAdv, consts.ChannelIdAllWx, consts.ChannelIdAllTT:
  304. // 所有广告渠道
  305. must = append(must, eapi.M{
  306. "terms": eapi.M{
  307. "channel_id": Channel.GetIdsByType(params.ChannelId),
  308. },
  309. })
  310. default:
  311. // 选择指定渠道
  312. must = append(must, eapi.M{
  313. "term": eapi.M{
  314. "channel_id": params.ChannelId,
  315. },
  316. })
  317. }
  318. //if params.ChannelId != "" {
  319. // if params.ChannelId == "1" {
  320. // mustNot = append(mustNot, eapi.M{
  321. // "term": eapi.M{
  322. // "channel_id": "0",
  323. // },
  324. // })
  325. // } else {
  326. // must = append(must, eapi.M{
  327. // "term": eapi.M{
  328. // "channel_id": params.ChannelId,
  329. // },
  330. // })
  331. // }
  332. //
  333. //}
  334. // 新用户
  335. if isNew {
  336. // 同一天
  337. if begin+86400 == end {
  338. must = append(must, eapi.M{
  339. "range": eapi.M{
  340. "user_created_at": eapi.M{
  341. "gte": begin,
  342. "lte": end,
  343. },
  344. },
  345. })
  346. } else {
  347. // 多天的,分析每天的新用户
  348. for i := begin; i < end; i += 86400 {
  349. var newMust = must
  350. newMust = append(newMust, eapi.M{
  351. "range": eapi.M{
  352. "user_created_at": eapi.M{
  353. "gte": i,
  354. "lte": i + 86400,
  355. },
  356. },
  357. })
  358. if len(mustNot) > 0 {
  359. where = eapi.M{
  360. "query": eapi.M{
  361. "bool": eapi.M{
  362. "must": newMust,
  363. "must_not": mustNot,
  364. },
  365. },
  366. }
  367. } else {
  368. where = eapi.M{
  369. "query": eapi.M{
  370. "bool": eapi.M{
  371. "must": newMust,
  372. },
  373. },
  374. }
  375. }
  376. res, err := eapi.Count(ctx, index, where)
  377. if err != nil {
  378. return 0, 0, err
  379. }
  380. if res > 0 {
  381. count += res
  382. }
  383. res2, err := eapi.Cardinality(ctx, index, where, "user_id")
  384. if err != nil {
  385. return 0, 0, err
  386. }
  387. if res2 > 0 {
  388. userCount += res2
  389. }
  390. }
  391. return
  392. }
  393. }
  394. if len(mustNot) > 0 {
  395. where = eapi.M{
  396. "query": eapi.M{
  397. "bool": eapi.M{
  398. "must": must,
  399. "must_not": mustNot,
  400. },
  401. },
  402. }
  403. } else {
  404. where = eapi.M{
  405. "query": eapi.M{
  406. "bool": eapi.M{
  407. "must": must,
  408. },
  409. },
  410. }
  411. }
  412. //logrus.Warnf("doReconnect index:%v\n where:%v\n", index, utility.DumpToJSON(where))
  413. res, err := eapi.Count(ctx, index, where)
  414. if err != nil {
  415. return 0, 0, err
  416. }
  417. res2, err := eapi.Cardinality(ctx, index, where, "user_id")
  418. if err != nil {
  419. return 0, 0, err
  420. }
  421. return res, res2, nil
  422. }
  423. func (s *sChapter) Reconnect(ctx *gin.Context, params forms.ChapterReconnectReq) (resp serializer.Response) {
  424. chapterMap := gmdata.GetChaptersMap()
  425. var (
  426. wg sync.WaitGroup
  427. errCh = make(chan error, len(chapterMap))
  428. )
  429. list := make([]forms.ChapterReconnectItem, 0)
  430. for _, v := range chapterMap {
  431. wg.Add(1)
  432. go func(v *gmdata.Chapter) {
  433. defer wg.Done()
  434. //count, users, err := s.doReconnect(ctx, params, int32(v.ID), int32(v.Difficulty), false)
  435. count, users, err := s.doReconnectFromDB(ctx, params, int32(v.ID), int32(v.Difficulty), false)
  436. if err != nil {
  437. errCh <- err
  438. return
  439. }
  440. //newCount, newUsers, err := s.doReconnect(ctx, params, int32(v.ID), int32(v.Difficulty), true)
  441. newCount, newUsers, err := s.doReconnectFromDB(ctx, params, int32(v.ID), int32(v.Difficulty), true)
  442. if err != nil {
  443. errCh <- err
  444. return
  445. }
  446. list = append(list, forms.ChapterReconnectItem{
  447. Id: len(list),
  448. ChapterId: int(v.ID),
  449. Chapter: Dash.GetChapterName(v.ID, v.Difficulty),
  450. DifficultyIndex: int(v.Difficulty),
  451. Difficulty: gmdata.GetDifficultName(v.Difficulty),
  452. DieCount: count,
  453. DieUserCount: users,
  454. NewDieCount: newCount,
  455. NewDieUserCount: newUsers,
  456. })
  457. }(v)
  458. }
  459. wg.Wait()
  460. select {
  461. case err1 := <-errCh:
  462. return serializer.Err(consts.CodeParamErr, err1.Error(), err1)
  463. default:
  464. }
  465. for k, v := range list {
  466. list[k].Index = v.ChapterId*1000 + v.DifficultyIndex
  467. }
  468. sort.Sort(forms.ChapterReconnectItemSlice(list))
  469. pageCount := int64(len(list))
  470. models := new(forms.ChapterReconnectRespData)
  471. models.Page = params.Page
  472. models.PerPage = params.PerPage
  473. models.PageCount = int64(math.Ceil(float64(pageCount) / float64(params.PerPage)))
  474. startInx := (models.Page - 1) * models.PerPage
  475. endInx := (models.Page) * models.PerPage
  476. if startInx < 0 {
  477. startInx = 0
  478. }
  479. if endInx > pageCount {
  480. endInx = pageCount
  481. }
  482. subList := list[startInx:endInx]
  483. models.List = subList
  484. return serializer.Suc(models)
  485. }