123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- package antiCheating
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "gadmin/config"
- "gadmin/internal/gorm/model"
- "gadmin/internal/gorm/query"
- "gadmin/package/gmdata"
- "gadmin/utility"
- "github.com/go-redis/redis"
- "github.com/sirupsen/logrus"
- "gorm.io/gen"
- "gorm.io/gorm"
- "io/ioutil"
- "os"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
- )
- type Combat struct {
- ctx context.Context
- sync.Mutex
- YearMonth string
- }
- type CombatOption func(*Combat)
- func WithYearMonth(yearMonth string) CombatOption {
- return func(combat *Combat) {
- combat.YearMonth = yearMonth
- }
- }
- func NewCombat(options ...CombatOption) *Combat {
- combat := &Combat{
- ctx: context.Background(),
- }
- for _, f := range options {
- f(combat)
- }
- return combat
- }
- func (c *Combat) Run() {
- c.Lock()
- defer c.Unlock()
- logrus.Infof("异常数据筛选...")
- c.getJson()
- c.getCombatData()
- }
- // 读取角色json
- var RoleMap map[int64]map[int]gmdata.Role
- var SkillGroupMap map[int64][]gmdata.Exskill
- func (c *Combat) getJson() {
- if len(RoleMap) != 0 && len(SkillGroupMap) != 0 {
- return
- }
- confPath := os.Getenv("JSON_PATH") + "/" + os.Getenv("JSON_VERSION")
- err := gmdata.LoadItemData(confPath, "Role.json", &gmdata.Roles)
- if err != nil {
- logrus.Errorf("读取Role.json错误:%v", err)
- return
- }
- RoleMap = make(map[int64]map[int]gmdata.Role)
- for _, item := range gmdata.Roles {
- if RoleMap[item.Type] == nil {
- RoleMap[item.Type] = make(map[int]gmdata.Role)
- }
- RoleMap[item.Type][item.Evolution] = item
- }
- err = gmdata.LoadItemData(confPath, "exskill.json", &gmdata.Exskills)
- if err != nil {
- logrus.Errorf("读取exskill.json错误:%v", err)
- return
- }
- SkillGroupMap = make(map[int64][]gmdata.Exskill)
- for _, item := range gmdata.Exskills {
- SkillGroupMap[int64(item.SkillGroup)] = append(SkillGroupMap[int64(item.SkillGroup)], item)
- }
- }
- func (c *Combat) coverRoleInfo(id int64) map[string]int {
- roleInfo := make(map[string]int)
- roleInfo["type"] = c.coverIDToSlice(id, 8) % 100
- roleInfo["evolution"] = c.coverIDToSlice(id, 4) % 1000
- roleInfo["level"] = c.coverIDToSlice(id, 1) % 1000
- roleInfo["break"] = int(id % 10)
- return roleInfo
- }
- func (c *Combat) coverIDToSlice(id int64, length int) int {
- idStr := strconv.FormatInt(id, 10)
- substring := idStr[:len(idStr)-length]
- result, _ := strconv.Atoi(substring)
- return result
- }
- func (c *Combat) getRoleSkillGroupIds(roleType int, evolution int, level int) []int {
- skillGroupIdArr := make([]int, 0)
- if role, ok := RoleMap[int64(roleType)][evolution]; ok {
- for i, item := range role.SkillUnlockLevel {
- if level >= item {
- skillGroupIdArr = append(skillGroupIdArr, role.Skill[i])
- }
- }
- }
- return skillGroupIdArr
- }
- func (c *Combat) getRoleSkillToID(skillGroupId, evolution int) (gmdata.Exskill, error) {
- if skillGroup, ok := SkillGroupMap[int64(skillGroupId)]; ok {
- sort.Slice(skillGroup, func(i, j int) bool {
- return skillGroup[i].SkillUnlock < skillGroup[j].SkillUnlock
- })
- exskill := gmdata.Exskill{}
- for _, skill := range skillGroup {
- if evolution >= 0 {
- exskill = skill
- }
- }
- return exskill, nil
- }
- return gmdata.Exskill{}, errors.New("未找到技能信息")
- }
- func (c *Combat) getMax(x, y float64) float64 {
- if x > y {
- return x
- }
- return y
- }
- // 获取当月战斗数据的player_id集合
- func (c *Combat) getCombatData() {
- table := config.DB.Scopes(model.ChapterLogsUserDetailTableSetDate(c.YearMonth))
- chapterModel := query.Use(table).ChapterLogsUserDetail.Table("")
- chapterQuery := chapterModel.WithContext(c.ctx)
- alarmModel := query.Use(config.DB).GameAlarmCombatLog
- alarmQuery := alarmModel.WithContext(c.ctx)
- //取lastId 先从redis,再从mysql
- lastIdMap, err := config.LogRedis.HGetAll("alarm:cheating:id").Result()
- if err != nil && !errors.Is(err, redis.Nil) {
- logrus.Errorf("redis获取作弊同步数据ID失败:%v", err)
- return
- }
- lastId, _ := strconv.ParseInt(lastIdMap["id"], 10, 64)
- yearMonth := lastIdMap["yearMonth"]
- if yearMonth != c.YearMonth {
- lastId = 0
- }
- if lastId == 0 {
- alarmInfo, err := alarmQuery.
- Where(alarmModel.YearMonth.Eq(c.YearMonth)).
- Order(alarmModel.OriginTime.Desc(), alarmModel.OriginID.Desc()).First()
- if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
- lastId = 0
- } else {
- lastId = alarmInfo.OriginID
- }
- }
- chapterList := make([]*model.ChapterLogsUserDetail, 0)
- chapterQuery = chapterQuery.WithContext(c.ctx).
- Select(chapterModel.ID, chapterModel.UserID, chapterModel.ServerID, chapterModel.Extra, chapterModel.EventAtNs)
- if lastId != 0 {
- chapterQuery = chapterQuery.Where(chapterModel.ID.Gt(lastId))
- }
- err = chapterQuery.FindInBatches(&chapterList, 1000, func(tx gen.Dao, batch int) error {
- // 批量从cos获取extra数据
- extraPaths := make([]string, 0)
- for _, item := range chapterList {
- if !strings.Contains(item.Extra, "{") {
- extraPaths = append(extraPaths, item.Extra)
- }
- }
- extraPathMap := &sync.Map{}
- var wg sync.WaitGroup
- smallPaths := utility.SplitStringsBySize(extraPaths, 100)
- for _, paths := range smallPaths {
- wg.Add(1)
- var wg1 sync.WaitGroup
- for _, path := range paths {
- wg1.Add(1)
- go func(path string) {
- defer wg1.Done()
- if response, err := config.GetCOSClient().Object.Get(context.Background(), path, nil); err != nil {
- logrus.Errorf("从cos中获取extra发生错误: %v", err)
- return
- } else {
- extraByt, err := ioutil.ReadAll(response.Body)
- response.Body.Close()
- if err != nil {
- logrus.Errorf("读取cos数据出错: %v", err)
- return
- } else {
- extraPathMap.Store(path, string(extraByt))
- }
- }
- }(path)
- }
- wg1.Wait()
- wg.Done()
- }
- wg.Wait()
- //遍历
- for _, item := range chapterList {
- var extra Extra
- extraStr := ""
- if !strings.Contains(item.Extra, "{") {
- if val, ok := extraPathMap.Load(item.Extra); ok {
- extraStr = val.(string)
- }
- } else {
- extraStr = item.Extra
- }
- err := json.Unmarshal([]byte(extraStr), &extra)
- if err != nil {
- logrus.Errorf("战斗数据解析失败: %v", err)
- continue
- }
- if len(extra.Harm) == 0 {
- continue
- }
- var tempHarm float64
- var data *model.GameAlarmCombatLog
- for _, harm := range extra.Harm {
- if harm[0] != 1 {
- continue
- }
- roleAttr := extra.Attr[harm[2]]
- roleInfo := c.coverRoleInfo(roleAttr.RoleId)
- if roleInfo["type"] != 1 {
- continue
- }
- skillGroupIds := c.getRoleSkillGroupIds(roleInfo["type"], roleInfo["evolution"], roleInfo["level"])
- if len(skillGroupIds) == 0 {
- continue
- }
- for _, skillGroupId := range skillGroupIds {
- skillInfo, err := c.getRoleSkillToID(skillGroupId, roleInfo["evolution"])
- if err != nil {
- return err
- }
- if skillInfo.SkillType == 1 {
- /* 倍率 */
- var rate = skillInfo.SkillValue[2]
- /** 伤害估值 */
- dd := float64(roleAttr.Attr.InitDps) * rate * 1.2
- // 如果实际伤害大于估值 说明异常
- if float64(harm[1]) > dd {
- //取最大值
- if tempHarm < float64(harm[1]) {
- tempHarm = float64(harm[1])
- ratio := fmt.Sprintf("%.2f", tempHarm/dd)
- ratioFloat, err := strconv.ParseFloat(ratio, 10)
- if err != nil {
- return err
- }
- roleInfoJson, err := json.Marshal(roleInfo)
- if err != nil {
- return err
- }
- data = &model.GameAlarmCombatLog{
- OriginID: item.ID,
- OriginTime: strconv.FormatInt(item.EventAtNs, 10),
- YearMonth: c.YearMonth,
- PlayerID: item.UserID,
- ServerID: item.ServerID,
- Dps: float64(roleAttr.Attr.InitDps), //秒伤
- Rate: rate, //技能倍率
- Valuation: dd, //估值伤害
- Damage: tempHarm, //实际伤害
- Ratio: ratioFloat, //实际伤害/估值伤害
- RoleInfo: string(roleInfoJson),
- AlarmData: item.Extra,
- CreatedAt: time.Now(),
- }
- }
- }
- }
- }
- }
- //循环harm完成
- if data != nil {
- err = alarmQuery.Create(data)
- if err != nil {
- logrus.Errorf("异常数据插入失败: %v", err)
- return err
- }
- }
- }
- //保存lastId至redis
- id := chapterList[len(chapterList)-1].ID
- err := config.LogRedis.HMSet("alarm:cheating:id", map[string]interface{}{"id": id, "yearMonth": c.YearMonth}).Err()
- if err != nil {
- logrus.Errorf("redis保存作弊同步数据ID失败")
- return err
- }
- time.Sleep(time.Millisecond * 100)
- return nil
- })
- if err != nil {
- logrus.Errorf("检测失败:%v", err)
- return
- }
- }
- type Extra struct {
- Attr []ExtraAttr `json:"attr"`
- Hp [][]int `json:"hp"`
- Harm [][]int64 `json:"harm"`
- }
- type ExtraAttr struct {
- RoleId int64 `json:"roleId"`
- State int `json:"state"`
- Attr ExtraAttrInfo `json:"attr"`
- }
- type ExtraAttrInfo struct {
- RoleId int `json:"roleId"`
- InitAtk int64 `json:"initAtk"`
- AtkUp float64 `json:"atkUp"`
- InitEleAtk int64 `json:"initEleAtk"`
- EleAtkUp float64 `json:"eleAtkUp"`
- BossUP float64 `json:"BossUP"`
- MonUp float64 `json:"MonUP"`
- InitDps int64 `json:"initDps"`
- }
|