combat.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. package antiCheating
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "gadmin/config"
  8. "gadmin/internal/gorm/model"
  9. "gadmin/internal/gorm/query"
  10. "gadmin/package/gmdata"
  11. "gadmin/utility"
  12. "github.com/go-redis/redis"
  13. "github.com/sirupsen/logrus"
  14. "gorm.io/gen"
  15. "gorm.io/gorm"
  16. "io/ioutil"
  17. "os"
  18. "sort"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "time"
  23. )
  24. type Combat struct {
  25. ctx context.Context
  26. sync.Mutex
  27. YearMonth string
  28. }
  29. type CombatOption func(*Combat)
  30. func WithYearMonth(yearMonth string) CombatOption {
  31. return func(combat *Combat) {
  32. combat.YearMonth = yearMonth
  33. }
  34. }
  35. func NewCombat(options ...CombatOption) *Combat {
  36. combat := &Combat{
  37. ctx: context.Background(),
  38. }
  39. for _, f := range options {
  40. f(combat)
  41. }
  42. return combat
  43. }
  44. func (c *Combat) Run() {
  45. c.Lock()
  46. defer c.Unlock()
  47. logrus.Infof("异常数据筛选...")
  48. c.getJson()
  49. c.getCombatData()
  50. }
  51. // 读取角色json
  52. var RoleMap map[int64]map[int]gmdata.Role
  53. var SkillGroupMap map[int64][]gmdata.Exskill
  54. func (c *Combat) getJson() {
  55. if len(RoleMap) != 0 && len(SkillGroupMap) != 0 {
  56. return
  57. }
  58. confPath := os.Getenv("JSON_PATH") + "/" + os.Getenv("JSON_VERSION")
  59. err := gmdata.LoadItemData(confPath, "Role.json", &gmdata.Roles)
  60. if err != nil {
  61. logrus.Errorf("读取Role.json错误:%v", err)
  62. return
  63. }
  64. RoleMap = make(map[int64]map[int]gmdata.Role)
  65. for _, item := range gmdata.Roles {
  66. if RoleMap[item.Type] == nil {
  67. RoleMap[item.Type] = make(map[int]gmdata.Role)
  68. }
  69. RoleMap[item.Type][item.Evolution] = item
  70. }
  71. err = gmdata.LoadItemData(confPath, "exskill.json", &gmdata.Exskills)
  72. if err != nil {
  73. logrus.Errorf("读取exskill.json错误:%v", err)
  74. return
  75. }
  76. SkillGroupMap = make(map[int64][]gmdata.Exskill)
  77. for _, item := range gmdata.Exskills {
  78. SkillGroupMap[int64(item.SkillGroup)] = append(SkillGroupMap[int64(item.SkillGroup)], item)
  79. }
  80. }
  81. func (c *Combat) coverRoleInfo(id int64) map[string]int {
  82. roleInfo := make(map[string]int)
  83. roleInfo["type"] = c.coverIDToSlice(id, 8) % 100
  84. roleInfo["evolution"] = c.coverIDToSlice(id, 4) % 1000
  85. roleInfo["level"] = c.coverIDToSlice(id, 1) % 1000
  86. roleInfo["break"] = int(id % 10)
  87. return roleInfo
  88. }
  89. func (c *Combat) coverIDToSlice(id int64, length int) int {
  90. idStr := strconv.FormatInt(id, 10)
  91. substring := idStr[:len(idStr)-length]
  92. result, _ := strconv.Atoi(substring)
  93. return result
  94. }
  95. func (c *Combat) getRoleSkillGroupIds(roleType int, evolution int, level int) []int {
  96. skillGroupIdArr := make([]int, 0)
  97. if role, ok := RoleMap[int64(roleType)][evolution]; ok {
  98. for i, item := range role.SkillUnlockLevel {
  99. if level >= item {
  100. skillGroupIdArr = append(skillGroupIdArr, role.Skill[i])
  101. }
  102. }
  103. }
  104. return skillGroupIdArr
  105. }
  106. func (c *Combat) getRoleSkillToID(skillGroupId, evolution int) (gmdata.Exskill, error) {
  107. if skillGroup, ok := SkillGroupMap[int64(skillGroupId)]; ok {
  108. sort.Slice(skillGroup, func(i, j int) bool {
  109. return skillGroup[i].SkillUnlock < skillGroup[j].SkillUnlock
  110. })
  111. exskill := gmdata.Exskill{}
  112. for _, skill := range skillGroup {
  113. if evolution >= 0 {
  114. exskill = skill
  115. }
  116. }
  117. return exskill, nil
  118. }
  119. return gmdata.Exskill{}, errors.New("未找到技能信息")
  120. }
  121. func (c *Combat) getMax(x, y float64) float64 {
  122. if x > y {
  123. return x
  124. }
  125. return y
  126. }
  127. // 获取当月战斗数据的player_id集合
  128. func (c *Combat) getCombatData() {
  129. table := config.DB.Scopes(model.ChapterLogsUserDetailTableSetDate(c.YearMonth))
  130. chapterModel := query.Use(table).ChapterLogsUserDetail.Table("")
  131. chapterQuery := chapterModel.WithContext(c.ctx)
  132. alarmModel := query.Use(config.DB).GameAlarmCombatLog
  133. alarmQuery := alarmModel.WithContext(c.ctx)
  134. //取lastId 先从redis,再从mysql
  135. lastIdMap, err := config.LogRedis.HGetAll("alarm:cheating:id").Result()
  136. if err != nil && !errors.Is(err, redis.Nil) {
  137. logrus.Errorf("redis获取作弊同步数据ID失败:%v", err)
  138. return
  139. }
  140. lastId, _ := strconv.ParseInt(lastIdMap["id"], 10, 64)
  141. yearMonth := lastIdMap["yearMonth"]
  142. if yearMonth != c.YearMonth {
  143. lastId = 0
  144. }
  145. if lastId == 0 {
  146. alarmInfo, err := alarmQuery.
  147. Where(alarmModel.YearMonth.Eq(c.YearMonth)).
  148. Order(alarmModel.OriginTime.Desc(), alarmModel.OriginID.Desc()).First()
  149. if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
  150. lastId = 0
  151. } else {
  152. lastId = alarmInfo.OriginID
  153. }
  154. }
  155. chapterList := make([]*model.ChapterLogsUserDetail, 0)
  156. chapterQuery = chapterQuery.WithContext(c.ctx).
  157. Select(chapterModel.ID, chapterModel.UserID, chapterModel.ServerID, chapterModel.Extra, chapterModel.EventAtNs)
  158. if lastId != 0 {
  159. chapterQuery = chapterQuery.Where(chapterModel.ID.Gt(lastId))
  160. }
  161. err = chapterQuery.FindInBatches(&chapterList, 1000, func(tx gen.Dao, batch int) error {
  162. // 批量从cos获取extra数据
  163. extraPaths := make([]string, 0)
  164. for _, item := range chapterList {
  165. if !strings.Contains(item.Extra, "{") {
  166. extraPaths = append(extraPaths, item.Extra)
  167. }
  168. }
  169. extraPathMap := &sync.Map{}
  170. var wg sync.WaitGroup
  171. smallPaths := utility.SplitStringsBySize(extraPaths, 100)
  172. for _, paths := range smallPaths {
  173. wg.Add(1)
  174. var wg1 sync.WaitGroup
  175. for _, path := range paths {
  176. wg1.Add(1)
  177. go func(path string) {
  178. defer wg1.Done()
  179. if response, err := config.GetCOSClient().Object.Get(context.Background(), path, nil); err != nil {
  180. logrus.Errorf("从cos中获取extra发生错误: %v", err)
  181. return
  182. } else {
  183. extraByt, err := ioutil.ReadAll(response.Body)
  184. response.Body.Close()
  185. if err != nil {
  186. logrus.Errorf("读取cos数据出错: %v", err)
  187. return
  188. } else {
  189. extraPathMap.Store(path, string(extraByt))
  190. }
  191. }
  192. }(path)
  193. }
  194. wg1.Wait()
  195. wg.Done()
  196. }
  197. wg.Wait()
  198. //遍历
  199. for _, item := range chapterList {
  200. var extra Extra
  201. extraStr := ""
  202. if !strings.Contains(item.Extra, "{") {
  203. if val, ok := extraPathMap.Load(item.Extra); ok {
  204. extraStr = val.(string)
  205. }
  206. } else {
  207. extraStr = item.Extra
  208. }
  209. err := json.Unmarshal([]byte(extraStr), &extra)
  210. if err != nil {
  211. logrus.Errorf("战斗数据解析失败: %v", err)
  212. continue
  213. }
  214. if len(extra.Harm) == 0 {
  215. continue
  216. }
  217. var tempHarm float64
  218. var data *model.GameAlarmCombatLog
  219. for _, harm := range extra.Harm {
  220. if harm[0] != 1 {
  221. continue
  222. }
  223. roleAttr := extra.Attr[harm[2]]
  224. roleInfo := c.coverRoleInfo(roleAttr.RoleId)
  225. if roleInfo["type"] != 1 {
  226. continue
  227. }
  228. skillGroupIds := c.getRoleSkillGroupIds(roleInfo["type"], roleInfo["evolution"], roleInfo["level"])
  229. if len(skillGroupIds) == 0 {
  230. continue
  231. }
  232. for _, skillGroupId := range skillGroupIds {
  233. skillInfo, err := c.getRoleSkillToID(skillGroupId, roleInfo["evolution"])
  234. if err != nil {
  235. return err
  236. }
  237. if skillInfo.SkillType == 1 {
  238. /* 倍率 */
  239. var rate = skillInfo.SkillValue[2]
  240. /** 伤害估值 */
  241. dd := float64(roleAttr.Attr.InitDps) * rate * 1.2
  242. // 如果实际伤害大于估值 说明异常
  243. if float64(harm[1]) > dd {
  244. //取最大值
  245. if tempHarm < float64(harm[1]) {
  246. tempHarm = float64(harm[1])
  247. ratio := fmt.Sprintf("%.2f", tempHarm/dd)
  248. ratioFloat, err := strconv.ParseFloat(ratio, 10)
  249. if err != nil {
  250. return err
  251. }
  252. roleInfoJson, err := json.Marshal(roleInfo)
  253. if err != nil {
  254. return err
  255. }
  256. data = &model.GameAlarmCombatLog{
  257. OriginID: item.ID,
  258. OriginTime: strconv.FormatInt(item.EventAtNs, 10),
  259. YearMonth: c.YearMonth,
  260. PlayerID: item.UserID,
  261. ServerID: item.ServerID,
  262. Dps: float64(roleAttr.Attr.InitDps), //秒伤
  263. Rate: rate, //技能倍率
  264. Valuation: dd, //估值伤害
  265. Damage: tempHarm, //实际伤害
  266. Ratio: ratioFloat, //实际伤害/估值伤害
  267. RoleInfo: string(roleInfoJson),
  268. AlarmData: item.Extra,
  269. CreatedAt: time.Now(),
  270. }
  271. }
  272. }
  273. }
  274. }
  275. }
  276. //循环harm完成
  277. if data != nil {
  278. err = alarmQuery.Create(data)
  279. if err != nil {
  280. logrus.Errorf("异常数据插入失败: %v", err)
  281. return err
  282. }
  283. }
  284. }
  285. //保存lastId至redis
  286. id := chapterList[len(chapterList)-1].ID
  287. err := config.LogRedis.HMSet("alarm:cheating:id", map[string]interface{}{"id": id, "yearMonth": c.YearMonth}).Err()
  288. if err != nil {
  289. logrus.Errorf("redis保存作弊同步数据ID失败")
  290. return err
  291. }
  292. time.Sleep(time.Millisecond * 100)
  293. return nil
  294. })
  295. if err != nil {
  296. logrus.Errorf("检测失败:%v", err)
  297. return
  298. }
  299. }
  300. type Extra struct {
  301. Attr []ExtraAttr `json:"attr"`
  302. Hp [][]int `json:"hp"`
  303. Harm [][]int64 `json:"harm"`
  304. }
  305. type ExtraAttr struct {
  306. RoleId int64 `json:"roleId"`
  307. State int `json:"state"`
  308. Attr ExtraAttrInfo `json:"attr"`
  309. }
  310. type ExtraAttrInfo struct {
  311. RoleId int `json:"roleId"`
  312. InitAtk int64 `json:"initAtk"`
  313. AtkUp float64 `json:"atkUp"`
  314. InitEleAtk int64 `json:"initEleAtk"`
  315. EleAtkUp float64 `json:"eleAtkUp"`
  316. BossUP float64 `json:"BossUP"`
  317. MonUp float64 `json:"MonUP"`
  318. InitDps int64 `json:"initDps"`
  319. }