package service import ( "database/sql" "encoding/json" "fmt" "gadmin/config" "gadmin/internal/admin/forms" "gadmin/internal/gorm/query" "gadmin/package/gmdata" "strconv" "time" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/xuri/excelize/v2" ) type PlayerService struct { ctx *gin.Context } const ( batchSize = 1000 ) func NewPlayerService(ctx *gin.Context) *PlayerService { return &PlayerService{ ctx: ctx, } } func (p *PlayerService) ExportStatistics(req forms.StatisticsExportReq) { // 调用本函数会导致线上服务器重启,暂屏蔽 return for i := 2; i >= 0; i-- { searchDay := time.Now().AddDate(0, -i, 0) //用户属性 playerAttrList, err := p.getUserInfoByMonth(searchDay) if err != nil { logrus.WithField("method", "ExportStatistics").Errorf("获取用户属性失败: %v", err) return } if len(playerAttrList) == 0 { return } //用户充值数据 playerRechargeMap, err := p.getUserRechargeData(searchDay) if err != nil { logrus.WithField("method", "ExportStatistics").Errorf("获取用户充值数据失败: %v", err) return } //用户属性数据清洗转换 playerInfoList := make([]*forms.PlayerInfo, len(playerAttrList)) // ====定义玩家参数map=== var equipMap map[string]int64 //装备 var roleArr []forms.PlayerRole //角色 var passRoom []forms.PlayerChapter //关卡记录 var bodyArr []forms.PlayerBody //部位 var talentMap map[string]int //天赋 var treasuresArr []forms.PlayerTreasure // ====定义玩家参数map=== for n, item := range playerAttrList { //处理数据 playerInfo := &forms.PlayerInfo{ PlayerId: item.PlayerId, AccID: item.AccID, NickName: item.NickName, ServerID: int(item.ServerID), Level: item.Level, Exp: item.Exp, Stamina: item.Stamina, Coin: item.Coin, Diamond: int64(item.Diamond), Lastlogintick: time.Unix(item.Lastlogintick, 0).Format("2006-01-02 15:04:05"), Lastlogouttick: time.Unix(item.Lastlogouttick, 0).Format("2006-01-02 15:04:05"), CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"), } //玩家武器 if item.FastEquips != "" { err = json.Unmarshal([]byte(item.FastEquips), &equipMap) if err != nil { logrus.WithField("method", "getUserExtraData").Errorf("玩家武器数据json umarshal错误: %v", err) return } playerWear := make([]gmdata.PlayerWear, 0) for _, equipId := range equipMap { equip := gmdata.PlayerWears[equipId] playerWear = append(playerWear, equip) } marshal, err := json.Marshal(playerWear) if err != nil { return } playerInfo.PlayerWear = string(marshal) } //玩家角色 if item.Roles != "" { err = json.Unmarshal([]byte(item.Roles), &roleArr) if err != nil { logrus.WithField("method", "getUserExtraData").Errorf("玩家角色数据json umarshal错误: %v", err) return } playerRole := make([]gmdata.PlayerRole, 0) for _, role := range roleArr { playerRole = append(playerRole, gmdata.PlayerRoles[role.Id]) } marshal, err := json.Marshal(playerRole) if err != nil { return } playerInfo.PlayerRole = string(marshal) } //玩家关卡 if item.PassRoom != "" { err = json.Unmarshal([]byte(item.PassRoom), &passRoom) if err != nil { logrus.WithField("method", "getUserExtraData").Errorf("玩家关卡数据json umarshal错误: %v", err) return } playerChapter := make([]gmdata.PlayerChapter, 0) for _, chapter := range passRoom { if chapter.Pass == true { playerChapter = append(playerChapter, gmdata.PlayerChapters[chapter.ID]) } } marshal, err := json.Marshal(playerChapter) if err != nil { return } playerInfo.PlayerChapter = string(marshal) } //玩家部位 if item.Body != "" { err = json.Unmarshal([]byte(item.Body), &bodyArr) if err != nil { logrus.WithField("method", "getUserExtraData").Errorf("玩家部位数据json umarshal错误: %v", err) return } playerBody := make([]gmdata.PlayerBody, 0) for _, body := range bodyArr { playerBody = append(playerBody, gmdata.PlayerBody{ Part: body.Part, Name: gmdata.BodyMap[body.Part], Level: body.Level, Step: body.Step, }) } marshal, err := json.Marshal(playerBody) if err != nil { return } playerInfo.PlayerBody = string(marshal) } //玩家天赋 if item.Talents != "" { err = json.Unmarshal([]byte(item.Talents), &talentMap) if err != nil { logrus.WithField("method", "getUserExtraData").Errorf("玩家天赋数据json umarshal错误: %v", err) return } playerTalent := make([]gmdata.PlayerTalent, 0) for tId, num := range talentMap { atoi, _ := strconv.Atoi(tId) talent := gmdata.PlayerTalents[atoi] talent.Number = num playerTalent = append(playerTalent, talent) } marshal, err := json.Marshal(playerTalent) if err != nil { return } playerInfo.PlayerTalent = string(marshal) } //玩家宝物 if item.Treasures != "" { err = json.Unmarshal([]byte(item.Treasures), &treasuresArr) if err != nil { logrus.WithField("method", "getUserExtraData").Errorf("玩家宝物数据json umarshal错误: %v", err) return } playerTreasure := make([]gmdata.PlayerTreasure, 0) for _, treasure := range treasuresArr { playerTreasure = append(playerTreasure, gmdata.PlayerTreasures[treasure.TId]) } marshal, err := json.Marshal(playerTreasure) if err != nil { return } playerInfo.PlayerTreasure = string(marshal) } if m, ok := playerRechargeMap[playerInfo.PlayerId]; ok { playerInfo.LastChargeAt = m.LastChargeAt playerInfo.FirstChargeAt = m.FirstChargeAt playerInfo.RechargeAmount = m.RechargeAmount playerInfo.RechargeCount = m.RechargeCount playerInfo.Total = m.Total playerInfo.Count = m.Count } //add playerInfoList[n] = playerInfo } p.exportToExcel(playerInfoList, searchDay, i) } } func (p *PlayerService) getUserInfoByMonth(searchDay time.Time) ([]forms.PlayerAttr, error) { searchTime := p.getSearchTime(searchDay) userInfos := make([]forms.PlayerAttr, 0) userInfoArr := make([]forms.PlayerAttr, 0) sql := "SELECT `player_attr`.`nickName` as nick_name,`player_attr`.`playerid` as player_id ,`player_attr`.`accId` as acc_id,`player_attr`.`serverId` as server_id,`player_attr`.`level`,`player_attr`.`exp`,`player_attr`.`stamina`,`player_attr`.`lastlogintick`, `player_attr`.`lastlogouttick`, `player_attr`.`create_time`, `player_material`.`coin`, `player_material`.`diamond`, `player_material`.`payCount` as pay_count, `player_material`.`fastEquips` as fast_equips, `player_material`.`roles`, `player_material`.`talents`, `player_material`.`treasures`, `player_material`.`body`, `player_material`.`passRoom` as pass_room FROM `player_attr` LEFT JOIN `player_material` ON `player_material`.`playerid` = `player_attr`.`playerid` WHERE `player_attr`.`create_time` >= ? AND `player_attr`.`create_time` <= ?" //sql2 := "SELECT `player_attr`.`playerid`,`player_attr`.`accId`,`player_attr`.`nickName`,`player_attr`.`serverId` FROM `player_attr` WHERE `player_attr`.`create_time` >= ?" var userInfo forms.PlayerAttr for _, db := range config.GDBGroup { rows, err := db.Raw(sql, searchTime.FirstDayTimeString, searchTime.LastDayTimesString).Rows() for rows.Next() { err = db.ScanRows(rows, &userInfo) if err != nil { return nil, err } userInfos = append(userInfos, userInfo) } if err != nil { return nil, err } userInfoArr = append(userInfoArr, userInfos...) } return userInfoArr, nil } func (p *PlayerService) getUserRechargeData(searchDay time.Time) (map[int64]*forms.PlayerRecharge, error) { searchTime := p.getSearchTime(searchDay) //充值数据 logDB.order 、 消费数据logDB.changedLog //1. 充值金额与次数 playerRecharge := make([]*forms.PlayerRecharge, 0) orderQuery := query.Use(config.DB).Order orderQueryDo := orderQuery.WithContext(p.ctx) err := orderQueryDo. Select(orderQuery.Money.Sum().As("total"), orderQuery.ID.Count().As("count"), orderQuery.PlayerID). Where(orderQuery.CreatedAt.Gte(searchDay)). Where(orderQuery.Status.Eq(1)). Where(orderQuery.CreatedAt.Between(time.Unix(searchTime.FirstDayTimestamp, 0), time.Unix(searchTime.LastDayTimestamp, 0))). Group(orderQuery.PlayerID). Scan(&playerRecharge) if err != nil { logrus.WithField("method", "getUserRechargeData").Errorf("查询玩家充值金额与次数失败: %v", err) return nil, err } //playerRecharge转为map playerRechargeMap := make(map[int64]*forms.PlayerRecharge) for _, item := range playerRecharge { playerRechargeMap[item.PlayerId] = item } // 第一次和最后一次充值 var RechargeSql = "SELECT\n first_order.id AS first_id,\n first_order.player_id AS player_id,\n first_order.created_at AS first_created_at,\n last_order.id AS last_id,\n last_order.created_at AS last_created_at\nFROM\n (SELECT\n id,\n player_id,\n created_at\n FROM\n orders\n WHERE\n created_at between @start and @end\n ORDER BY\n created_at\n LIMIT 1) AS first_order\nJOIN\n (SELECT\n id,\n player_id,\n created_at\n FROM\n orders\n WHERE\n created_at between @start and @end\n ORDER BY\n created_at DESC\n LIMIT 1) AS last_order\nON\n first_order.player_id = last_order.player_id" err = config.DB.Raw(RechargeSql, sql.Named("start", searchTime.FirstDayTimeString), sql.Named("end", searchTime.LastDayTimesString)).Scan(&playerRecharge).Error if err != nil { logrus.WithField("method", "getUserRechargeData").Errorf("查询玩家充值时间失败: %v", err) return nil, err } for _, item := range playerRecharge { if recharge, ok := playerRechargeMap[item.PlayerId]; ok { recharge.FirstChargeAt = item.FirstChargeAt recharge.LastChargeAt = item.LastChargeAt recharge.RechargeCount = item.RechargeCount recharge.RechargeAmount = item.RechargeAmount } } return playerRechargeMap, nil } func (p *PlayerService) getSearchTime(searchDay time.Time) forms.SearchTime { // 获取月份第一天 00:00:00 时间戳 firstDayOfMonth := time.Date(searchDay.Year(), searchDay.Month(), 1, 0, 0, 0, 0, time.Local) firstDayTimestamp := firstDayOfMonth.Unix() firstDayTimeString := firstDayOfMonth.Format("2006-01-02 15:04:05") fmt.Println("月份第一天 00:00:00 时间戳:", firstDayTimestamp) // 获取月份最后一天 23:59:59 时间戳 lastDayOfMonth := firstDayOfMonth.AddDate(0, 1, -1) lastDayTimestamp := lastDayOfMonth.Add(time.Duration(23*time.Hour + 59*time.Minute + 59*time.Second)).Unix() lastDayTimeString := lastDayOfMonth.Add(time.Duration(23*time.Hour + 59*time.Minute + 59*time.Second)).Format("2006-01-02 15:04:05") fmt.Println("月份最后一天 23:59:59 时间戳:", lastDayTimestamp) return forms.SearchTime{ FirstDayTimestamp: firstDayTimestamp, FirstDayTimeString: firstDayTimeString, LastDayTimestamp: lastDayTimestamp, LastDayTimesString: lastDayTimeString, } } func (p *PlayerService) exportToExcel(dataList []*forms.PlayerInfo, searchDay time.Time, n int) { searchName := searchDay.Format("200601") f := excelize.NewFile() sheetName := fmt.Sprintf("sheet_%s_%d", searchName, n) f.NewSheet(sheetName) streamWriter, err := f.NewStreamWriter(sheetName) if err != nil { logrus.WithField("method", "writeToFile").Errorf("创建流式写入器失败: %v", err) return } // 写入表头 row := []interface{}{"玩家ID", "账号ID", "服务器ID", "昵称", "等级", "经验", "体力", "金币", "钻石", "最后登录", "最后登出", "注册时间", "首充时间", "最后充值时间", "充值总额", "充值次数", "武器", "角色", "部位", "天赋", "宝物", "关卡"} if err := streamWriter.SetRow("A1", row); err != nil { logrus.WithField("method", "writeToFile").Errorf("写入表头失败: %v", err) return } // 写入数据 for i, playerInfo := range dataList { row := []interface{}{ playerInfo.PlayerId, playerInfo.AccID, playerInfo.ServerID, playerInfo.NickName, playerInfo.Level, playerInfo.Exp, playerInfo.Stamina, playerInfo.Coin, playerInfo.Diamond, playerInfo.Lastlogintick, playerInfo.Lastlogouttick, playerInfo.CreateTime, playerInfo.FirstChargeAt, playerInfo.LastChargeAt, playerInfo.RechargeAmount, playerInfo.RechargeCount, playerInfo.PlayerWear, playerInfo.PlayerRole, playerInfo.PlayerBody, playerInfo.PlayerTalent, playerInfo.PlayerTreasure, playerInfo.PlayerChapter, } if err := streamWriter.SetRow(fmt.Sprintf("A%d", i+2), row); err != nil { logrus.WithField("method", "writeToFile").Errorf("写入数据失败: %v", err) return } } if err := streamWriter.Flush(); err != nil { logrus.WithField("method", "writeToFile").Errorf("刷新流式写入器失败: %v", err) return } if err := f.SaveAs("./player_statistics.xlsx"); err != nil { logrus.WithField("method", "writeToFile").Errorf("保存文件失败: %v", err) return } }