瀏覽代碼

增肌部分功能

wucan 1 天之前
父節點
當前提交
5d509afa88

+ 26 - 0
bootstrap/cron.go

@@ -0,0 +1,26 @@
+package bootstrap
+
+import (
+	"designs/crons"
+	"designs/global"
+	"fmt"
+	"github.com/robfig/cron/v3"
+	"time"
+)
+
+func InitializeCron() {
+	global.App.Cron = cron.New(cron.WithSeconds())
+
+	go func() {
+		global.App.Cron.AddFunc("0 0 2 * * *", func() {
+			fmt.Println(time.Now())
+		})
+
+		global.App.Cron.AddFunc("0 0 1 * * *", crons.ActiveDelete)         //清理用户在线数据文件夹
+		global.App.Cron.AddFunc("0 0 2 * * *", crons.OnlineDatabaseDelete) //清理用户在线数据数据库
+		global.App.Cron.AddFunc("0 0 1 * * *", crons.AdsDataSummary)       //汇总昨日的广告数据
+		global.App.Cron.Start()
+		defer global.App.Cron.Stop()
+		select {}
+	}()
+}

+ 224 - 11
controller/v1/file.go

@@ -11,18 +11,21 @@ import (
 	"github.com/tencentyun/cos-go-sdk-v5"
 	"net/http"
 	"net/url"
+	"os"
 	"path/filepath"
+	"strings"
 	"time"
 )
 
 func FileList(c *gin.Context) {
 	form := request.Check(c, &struct {
+		Dir    string `form:"dir" binding:"required"`
 		Offset *int   `form:"offset" binding:"required"`
 		Limit  *int   `form:"limit" binding:"required"`
 		Search string `form:"search" binding:""`
 	}{})
 
-	query := global.App.DB.Table("file")
+	query := global.App.DB.Table("file").Where("dir", form.Dir)
 	if form.Search != "" {
 		query = query.Where("fileName", "like", "%"+form.Search+"%")
 	}
@@ -46,10 +49,60 @@ func FileList(c *gin.Context) {
 	})
 }
 
+func DirDownload(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Dir string `form:"dir" binding:"required"`
+	}{})
+
+	data, err := DirExplode(form.Dir)
+	if err != nil {
+		response.Fail(c, 1001, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": data,
+	})
+}
+
+func DirExplode(dir string) (map[string]interface{}, error) {
+
+	data := make(map[string]interface{})
+
+	var files []model.File
+	err := global.App.DB.Table("file").Where("dir", dir).Scan(&files).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	for _, file := range files {
+		if file.Type == 2 {
+			data[file.FileName], _ = DirExplode(file.Dir)
+		} else {
+			data[file.FileName] = file.CosPath
+		}
+	}
+
+	return data, nil
+}
+
 func FileDelete(c *gin.Context) {
 	form := request.Check(c, &struct {
 		FileId int `form:"fileId" json:"fileId" binding:"required"`
 	}{})
+	var file model.File
+	global.App.DB.Table("file").Where("id", form.FileId).Find(&file)
+
+	if file.Type == 1 {
+		//删除本地文件和云端文件  (本地文件暂时不删除,作为备份)
+		err := ServiceFileDelete(file.CosPath)
+		if err != nil {
+			response.Fail(c, 2001, "云端文件删除失败"+err.Error())
+			return
+		}
+	}
+
 	var d interface{}
 	err := global.App.DB.Table("file").Where("id", form.FileId).Delete(d).Error
 	if err != nil {
@@ -57,14 +110,12 @@ func FileDelete(c *gin.Context) {
 		return
 	}
 
-	//删除本地文件和云端文件
-
 	response.Success(c, gin.H{})
-
 }
 
 func LocalFileToService(c *gin.Context) {
 	form := request.Check(c, &struct {
+		Dir      string `form:"dir" json:"dir" binding:"required"`
 		FilePath string `form:"filePath" json:"filePath" binding:"required"`
 	}{})
 
@@ -84,6 +135,8 @@ func LocalFileToService(c *gin.Context) {
 		UpdatedAt: now,
 		FileName:  filepath.Base(form.FilePath),
 		LocalPath: form.FilePath,
+		Type:      1,
+		Dir:       form.Dir,
 		CosPath:   path,
 	}).Error
 	if err != nil {
@@ -96,32 +149,192 @@ func LocalFileToService(c *gin.Context) {
 	})
 }
 
+func UploadToCos(c *gin.Context) {
+	// 获取文件
+	file, err := c.FormFile("file")
+	if err != nil {
+		response.Fail(c, 400, "未选择文件")
+		return
+	}
+
+	//获取dir
+	dir := c.PostForm("dir")
+	if dir == "" {
+		dir = "/"
+	} else if dir != "/" { //格式化
+		dir = "/" + strings.Trim(dir, "/") + "/"
+	}
+
+	// 存储路径生成
+	saveDir := fmt.Sprintf("uploads/%s", dir)
+	os.MkdirAll(saveDir, 0755)
+
+	savePath := filepath.Join(saveDir, file.Filename)
+	// 保存文件
+	if err := c.SaveUploadedFile(file, savePath); err != nil {
+		response.Fail(c, 500, "文件保存失败")
+		return
+	}
+
+	path, err := FileToService(savePath)
+	if err != nil {
+		response.Fail(c, 1004, err.Error())
+		return
+	}
+
+	now := model.XTime{
+		Time: time.Now(),
+	}
+
+	//存储文件夹
+	var dirFile model.File
+
+	base := filepath.Base(dir)
+	dirPath := strings.TrimSuffix(dir, base+"/")
+
+	fmt.Println("dirPath", dirPath)
+	global.App.DB.Table("file").Where("type", 2).Where("fileName", base).Where("dir", dirPath).First(&dirFile)
+	if dirFile.Id == 0 {
+		err := global.App.DB.Table("file").Create(&model.File{
+			CreatedAt: now,
+			UpdatedAt: now,
+			FileName:  base,
+			Dir:       dirPath,
+			LocalPath: "",
+			Type:      2,
+			CosPath:   "",
+		}).Error
+		if err != nil {
+			response.Fail(c, 2001, err.Error())
+			return
+		}
+	}
+
+	//存储到mysql
+	var files model.File
+	global.App.DB.Table("file").Where("fileName", filepath.Base(savePath)).Where("dir", dir).Find(&files)
+
+	if files.Id != 0 {
+		err = global.App.DB.Table("file").Where("id", files.Id).Updates(map[string]interface{}{
+			"localPath": savePath,
+			"updatedAt": now,
+			"fileName":  filepath.Base(savePath),
+			"dir":       dir,
+			"cosPath":   path,
+		}).Error
+	} else {
+		err = global.App.DB.Table("file").Create(&model.File{
+			CreatedAt: now,
+			UpdatedAt: now,
+			FileName:  filepath.Base(savePath),
+			LocalPath: savePath,
+			Type:      1,
+			Dir:       dir,
+			CosPath:   path,
+		}).Error
+	}
+
+	if err != nil {
+		response.Fail(c, 2001, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"file_path": path,
+	})
+}
+
+func CreateDir(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Dir     string `form:"dir" json:"dir" binding:"required"`
+		DirName string `form:"dirName" json:"dirName" binding:"required"`
+	}{})
+
+	count := strings.Count(form.Dir, "/")
+	if count >= 3 {
+		response.Fail(c, 1002, "文件夹的层级不能超过三级")
+		return
+	}
+
+	now := model.XTime{
+		Time: time.Now(),
+	}
+
+	//存储到mysql
+	err := global.App.DB.Table("file").Create(&model.File{
+		CreatedAt: now,
+		UpdatedAt: now,
+		FileName:  form.DirName,
+		Dir:       form.Dir,
+		LocalPath: "",
+		Type:      2,
+		CosPath:   "",
+	}).Error
+	if err != nil {
+		response.Fail(c, 2001, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+const CosUrl = "https://chunhao-1257323087.cos.ap-shanghai.myqcloud.com"
+const CosServiceUrl = "https://service.cos.myqcloud.com"
+const SecretID = "AKIDgQ2TzTq381nQrAJKBC8tSqCNM9lBQTQB"
+const SecretKey = "gBVWO4dzCD5qeKoy30phTBi7NXYYM2b0"
+
 func FileToService(filePath string) (string, error) {
 	// 将 examplebucket-1250000000 和 COS_REGION 修改为用户真实的信息
 	// 存储桶名称,由 bucketname-appid 组成,appid 必须填入,可以在 COS 控制台查看存储桶名称。https://console.cloud.tencent.com/cos5/bucket
 	// COS_REGION 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket, 关于地域的详情见 https://cloud.tencent.com/document/product/436/6224
-	u, _ := url.Parse("https://chunhao-1257323087.cos.ap-shanghai.myqcloud.com")
+	u, _ := url.Parse(CosUrl)
 	// 用于 Get Service 查询,默认全地域 service.cos.myqcloud.com
-	su, _ := url.Parse("https://service.cos.myqcloud.com")
+	su, _ := url.Parse(CosServiceUrl)
 	b := &cos.BaseURL{BucketURL: u, ServiceURL: su}
 	// 1.永久密钥
 	client := cos.NewClient(b, &http.Client{
 		Transport: &cos.AuthorizationTransport{
-			SecretID:  "AKIDgQ2TzTq381nQrAJKBC8tSqCNM9lBQTQB", // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
-			SecretKey: "gBVWO4dzCD5qeKoy30phTBi7NXYYM2b0",     // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
+			SecretID:  SecretID,  // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
+			SecretKey: SecretKey, // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
 		},
 	})
 
 	key := filepath.Base(filePath)
+	fmt.Println("key", key)
+	fmt.Println("filePath", filePath)
 	res, _, err := client.Object.Upload(
-		context.Background(), key, filePath, nil,
+		context.Background(), filePath, filePath, nil,
 	)
 	fmt.Println(res)
 	if err != nil {
 		return "", err
 	}
 
-	ourl := client.Object.GetObjectURL(key)
+	//ourl := client.Object.GetObjectURL(key)
+
+	return res.Location, nil
+}
+
+func ServiceFileDelete(cosPath string) error {
+	u, _ := url.Parse(CosUrl)
+	// 用于 Get Service 查询,默认全地域 service.cos.myqcloud.com
+	su, _ := url.Parse(CosServiceUrl)
+	b := &cos.BaseURL{BucketURL: u, ServiceURL: su}
+	// 1.永久密钥
+	client := cos.NewClient(b, &http.Client{
+		Transport: &cos.AuthorizationTransport{
+			SecretID:  SecretID,  // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
+			SecretKey: SecretKey, // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140
+		},
+	})
+
+	res, err := client.Object.Delete(
+		context.Background(), cosPath,
+	)
+	fmt.Println(res)
+	if err != nil {
+		return err
+	}
 
-	return ourl.Host + ourl.Path, nil
+	return nil
 }

+ 58 - 2
controller/v1/gameAction.go

@@ -12,6 +12,7 @@ import (
 	"sort"
 	"strconv"
 	"strings"
+	"time"
 )
 
 func UserActionList(c *gin.Context) {
@@ -165,12 +166,15 @@ func UserActionOptionList(c *gin.Context) {
 
 	form.EndTime = form.EndTime + " 23:59:59"
 
+	now := time.Now()
 	//查出用户登录数据
 	userLogin, err := GetUserLogin(form.Gid, form.StartTime, form.EndTime)
 	if err != nil {
 		response.Fail(c, 1003, err.Error())
 		return
 	}
+
+	fmt.Println(time.Since(now))
 	userLoginCount := len(userLogin)
 	//查询出所有的事件选项
 	var actionOption []struct {
@@ -190,7 +194,7 @@ func UserActionOptionList(c *gin.Context) {
 		response.Fail(c, 1004, err.Error())
 		return
 	}
-
+	fmt.Println(time.Since(now))
 	//循环得出选项
 	optionList := make(map[string]int)
 	for _, v := range actionOption {
@@ -240,7 +244,7 @@ func UserActionOptionList(c *gin.Context) {
 	for _, v := range optionName {
 		optionIdToName[v.OptionId] = v.OptionName
 	}
-
+	fmt.Println(time.Since(now))
 	for k, v := range optionList {
 		var ActiveUserRate float64
 		var LoginActiveRate float64
@@ -549,3 +553,55 @@ func UserActionDetailDistribution(c *gin.Context) {
 		return
 	}
 }
+
+func CopyGameAction(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Gid    string `form:"gid" binding:"required"`
+		NewGid string `form:"newGid" binding:"required"`
+	}{})
+
+	//读取出gid对应的配置
+	var gameAction []model.GameAction
+	global.App.DB.Table("game_action").Where("gid", form.Gid).Scan(&gameAction)
+
+	now := model.XTime{
+		Time: time.Now(),
+	}
+
+	for _, v := range gameAction {
+		//复制
+		newAction := model.GameAction{
+			Gid:        form.NewGid,
+			ActionId:   v.ActionId,
+			ActionName: v.ActionName,
+			Status:     v.Status,
+			Remark:     v.Remark,
+			CreatedAt:  now,
+			UpdatedAt:  now,
+		}
+
+		global.App.DB.Table("game_action").Create(&newAction)
+
+		//查询出有无对应的option
+		var actionOption []model.GameActionOption
+		global.App.DB.Table("game_action_option").Where("actionId", v.ID).Scan(&actionOption)
+		fmt.Println(actionOption, v.ID)
+		if actionOption != nil {
+			for _, option := range actionOption {
+				newActionOption := model.GameActionOption{
+					ActionId:   newAction.ID,
+					OptionId:   option.OptionId,
+					OptionName: option.OptionName,
+					OptionType: option.OptionType,
+					Status:     option.Status,
+					CreatedAt:  now,
+					UpdatedAt:  now,
+				}
+
+				global.App.DB.Table("game_action_option").Create(&newActionOption)
+			}
+		}
+	}
+
+	response.Success(c, gin.H{})
+}

+ 104 - 0
controller/v1/gameConfig.go

@@ -11,7 +11,10 @@ import (
 	"encoding/json"
 	"github.com/gin-gonic/gin"
 	"github.com/go-playground/validator/v10"
+	"os"
+	"path/filepath"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -180,6 +183,107 @@ func GetGidConfig(c *gin.Context) {
 	})
 }
 
+func GidList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Search string `form:"search" binding:"" json:"search"`
+		Active bool   `form:"active" binding:"" json:"active"`
+	}{})
+
+	var gameList []struct {
+		Id       int    `json:"id" gorm:"primary_key"`
+		Pid      string `json:"pid" gorm:"column:pid; "`
+		Gid      string `json:"gid" gorm:"column:gid; "`
+		GameName string `json:"gameName" gorm:"column:gameName; "`
+	}
+	query := global.App.DB.Table("game")
+
+	if form.Search != "" {
+		query = query.Where("gid", "like", "%"+form.Search+"%").
+			Or("pid LIKE ?", "%"+form.Search+"%").
+			Or("gameName LIKE ?", "%"+form.Search+"%")
+	}
+
+	if form.Active == true {
+		//获取近七天有数据的gid
+		ActiveGid := GetActiveGid()
+		query = query.WhereIn("gid", ActiveGid)
+	}
+
+	err := query.Select("id", "gid", "gameName").Where("id", ">", 0).Scan(&gameList).Error
+	if err != nil {
+		response.Fail(c, 1002, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data":  gameList,
+		"count": len(gameList),
+	})
+
+}
+
+func GetActiveGid() []string {
+	var activeGidList []string
+
+	key := "activeGidList"
+	activeGid, _ := global.App.Redis.Get(context.Background(), key).Result()
+
+	activeGidList = strings.Split(activeGid, ",")
+
+	if len(activeGidList) <= 2 {
+		var gidList []string
+
+		//重新读取数据
+		var dir string
+		if config.Get("app.local") == "local" {
+			//url = "mongodb://localhost:27017"
+			dir = "storage"
+		} else {
+			dir = "/www/wwwroot/chunhao_receive/storage"
+		}
+
+		now := time.Now()
+
+		for i := 0; i <= 7; i++ {
+			date := now.AddDate(0, 0, -i).Format("2006-01-02")
+			//读取对应的文件夹
+			dirPath := filepath.Join(dir, date)
+			dateDir, _ := os.ReadDir(dirPath)
+			//fmt.Println(dirPath, dateDir)
+
+			for _, v := range dateDir {
+
+				fileName := v.Name()
+
+				fileNameSplit := strings.Split(fileName, "_")
+
+				if len(fileNameSplit) < 2 {
+					continue
+				}
+				last := fileNameSplit[len(fileNameSplit)-1]
+				gid := strings.TrimRight(fileName, "_"+last)
+
+				if !utils.InArray(gid, gidList) {
+					gidList = append(gidList, gid)
+				}
+			}
+		}
+
+		if len(gidList) > 0 {
+
+			var gidString string
+			for _, gid := range gidList {
+				gidString = gid + ","
+			}
+			global.App.Redis.Set(context.Background(), key, gidString, time.Second*3600)
+			activeGidList = gidList
+		}
+
+	}
+
+	return activeGidList
+}
+
 type ActionOption struct {
 	OptionName   string `json:"optionName" form:"optionName" binding:"required"`
 	OptionId     string `json:"optionId" form:"optionId" binding:"required"`

+ 182 - 0
controller/v1/permission.go

@@ -0,0 +1,182 @@
+package v1
+
+import (
+	"crypto/rand"
+	"designs/app/common/request"
+	"designs/app/common/response"
+	"designs/global"
+	"designs/model"
+	"encoding/json"
+	"github.com/gin-gonic/gin"
+	"math/big"
+	"time"
+)
+
+func AdminList(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Offset int    `form:"offset" binding:""`
+		Limit  int    `form:"limit" binding:""`
+		Search string `form:"search" binding:""`
+	}{})
+
+	query := global.App.DB.Table("admin")
+
+	if form.Search != "" {
+		query = query.WhereRaw("account LIKE ?", "%"+form.Search+"%")
+	}
+
+	var admin []model.Admin
+
+	type adminRes struct {
+		ID         int         `json:"id" gorm:"not null;"`
+		Account    string      `json:"account" gorm:"not null;"`
+		Name       string      `json:"name" gorm:"not null;"`
+		CreatedAt  model.XTime `json:"createdAt" gorm:"column:createdAt;"`
+		UpdatedAt  model.XTime `json:"updatedAt" gorm:"column:updatedAt;"`
+		Permission interface{} `json:"permission" gorm:""`
+	}
+
+	var count int64
+	err := query.Count(&count).Error
+	if err != nil {
+		response.Fail(c, 501, err.Error())
+		return
+	}
+
+	err = query.Offset(form.Offset).Select("id", "account", "name", "createdAt", "updatedAt", "permission").Limit(form.Limit).Scan(&admin).Error
+	if err != nil {
+		response.Fail(c, 501, err.Error())
+		return
+	}
+
+	var res []adminRes
+
+	for _, v := range admin {
+		var PermissionSlice []string
+
+		json.Unmarshal([]byte(v.Permission), &PermissionSlice)
+
+		res = append(res, adminRes{
+			ID:         v.ID,
+			Account:    v.Account,
+			Name:       v.Name,
+			CreatedAt:  v.CreatedAt,
+			UpdatedAt:  v.UpdatedAt,
+			Permission: PermissionSlice,
+		})
+	}
+
+	response.Success(c, gin.H{"data": res, "count": count})
+}
+
+func RandomString(length int) (string, error) {
+	const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
+	b := make([]byte, length)
+	for i := range b {
+		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
+		if err != nil {
+			return "", err
+		}
+		b[i] = charset[n.Int64()]
+	}
+	return string(b), nil
+}
+
+func SetAdmin(c *gin.Context) {
+	form := request.Check(c, &struct {
+		Account string `form:"account" binding:"required"`
+		Name    string `form:"name" binding:"required"`
+		//Password   string   `form:"password" binding:"required"`
+		Permission []string `form:"permission" binding:"required"`
+	}{})
+
+	var admin model.Admin
+	global.App.DB.WhereRaw("account = ?", form.Account).First(&admin)
+	if admin.ID != 0 {
+		response.Fail(c, 1001, "账户名称重复")
+		return
+	}
+
+	password, _ := RandomString(10)
+
+	now := model.XTime{Time: time.Now()}
+	admin.Account = form.Account
+	admin.Password = password
+	admin.Name = form.Name
+	admin.CreatedAt = now
+	admin.UpdatedAt = now
+	p, _ := json.Marshal(form.Permission)
+	admin.Permission = string(p)
+
+	err := global.App.DB.Table("admin").Create(&admin).Error
+	if err != nil {
+		response.Fail(c, 502, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{
+		"data": map[string]interface{}{
+			"password": password,
+		},
+	})
+}
+
+func DeleteAdmin(c *gin.Context) {
+	form := request.Check(c, &struct {
+		AdminId int `form:"adminId" binding:"required"`
+	}{})
+
+	if form.AdminId == 1 {
+		response.Fail(c, 501, "默认管理员无法删除")
+		return
+	}
+	var d interface{}
+	err := global.App.DB.Table("admin").Where("id", form.AdminId).Delete(d).Error
+	if err != nil {
+		response.Fail(c, 502, err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}
+
+func UpdateAdmin(c *gin.Context) {
+	form := request.Check(c, &struct {
+		AdminId    int      `form:"adminId" binding:"required"`
+		Name       string   `form:"name" binding:"required"`
+		Permission []string `form:"permission" binding:""`
+		Password   string   `form:"password" binding:""`
+	}{})
+
+	update := make(map[string]interface{})
+
+	if form.AdminId == 1 {
+		response.Fail(c, 501, "默认管理员无法编辑")
+		return
+	}
+
+	if form.Permission != nil {
+		p, _ := json.Marshal(form.Permission)
+		update["permission"] = string(p)
+	}
+	if form.Password != "" {
+		update["password"] = form.Password
+	}
+	if form.Name != "" {
+		update["name"] = form.Name
+	}
+
+	if len(form.Permission) == 0 {
+		response.Fail(c, 501, "没有更新")
+		return
+	}
+	update["updatedAt"] = time.Now()
+
+	err := global.App.DB.Table("admin").Where("id", form.AdminId).Updates(update).Error
+	if err != nil {
+		response.Fail(c, 502, "更新失败"+err.Error())
+		return
+	}
+
+	response.Success(c, gin.H{})
+}

+ 71 - 10
controller/v1/user.go

@@ -11,6 +11,7 @@ import (
 	"designs/service"
 	"designs/utils"
 	"fmt"
+	"github.com/go-redis/redis/v8"
 	"go.mongodb.org/mongo-driver/v2/bson"
 	"os"
 	"path/filepath"
@@ -77,6 +78,11 @@ func Login(c *gin.Context) {
 		return
 	}
 
+	var isSuper bool
+	if admin.ID == 1 {
+		isSuper = true
+	}
+
 	//获取token
 	token, err := common.GetJwtToken(form.UserName, 0)
 	//刷新refreshtoken
@@ -90,6 +96,7 @@ func Login(c *gin.Context) {
 	response.Success(c, gin.H{
 		"token":        token,
 		"refreshToken": refresh,
+		"isSuper":      isSuper,
 		"loginTime":    time.Now().Unix(),
 		"ip":           c.ClientIP(),
 	})
@@ -137,37 +144,91 @@ func UserList(c *gin.Context) {
 	form := request.Check(c, &struct {
 		Gid    string `form:"gid" json:"gid" binding:""`
 		Pf     string `form:"pf" json:"pf" binding:""`
+		Search string `form:"search" json:"search" binding:""`
 		Offset int    `form:"offset" json:"offset" binding:""`
 		Limit  int    `form:"limit" json:"limit" binding:"required"`
 	}{})
 
+	var res []User
 	//读取表
 	userTotalKey := config.Get("app.user_total")
 	if form.Gid != "" && form.Pf != "" {
 		userTotalKey = config.Get("app.user_total") + ":" + form.Gid + ":" + form.Pf
 	}
 
-	data, err := global.App.Redis.ZRevRangeWithScores(context.Background(), userTotalKey, int64(form.Offset), int64(form.Offset+form.Limit)-1).Result()
+	//读取黑名单
+	blackListKey := config.Get("app.black_list_table")
+	black, err := global.App.Redis.ZRange(context.Background(), blackListKey, 0, -1).Result()
 	if err != nil {
-		response.Fail(c, 1001, err.Error())
+		response.Fail(c, 1003, "读取黑名单列表失败"+err.Error())
 		return
 	}
 
-	count, err := global.App.Redis.ZCard(context.Background(), userTotalKey).Result()
-	if err != nil {
-		response.Fail(c, 1001, err.Error())
+	var data []redis.Z
+	if form.Search != "" {
+		data, _ = global.App.Redis.ZRevRangeWithScores(context.Background(), userTotalKey, 0, 1000000).Result()
+
+		//读取出所有的数据,然后过滤nickname
+		for _, val := range data {
+			userKey := val.Member.(string)
+			userData, err := global.App.Redis.HGetAll(context.Background(), userKey).Result()
+			if err != nil {
+				global.App.Log.Error("GetUserData err")
+				response.Fail(c, 1003, "GetUserData err"+err.Error())
+				return
+			}
+
+			userMsg := strings.Split(userKey, ":")
+			openId := userMsg[len(userMsg)-1]
+			gid := userMsg[0]
+			pf := userMsg[1]
+
+			if userData["nickname"] == form.Search || openId == form.Search {
+
+				//查看是否在黑名单
+				var inBlack bool
+				if utils.InArray(userKey, black) {
+					inBlack = true
+				}
+
+				//查看是否加了权限
+				optionKey := config.Get("app.option_key") + gid + ":" + pf + ":" + openId
+				option, err := global.App.Redis.HGetAll(context.Background(), optionKey).Result()
+				if err != nil {
+					response.Fail(c, 1003, err.Error())
+					return
+				}
+
+				res = append(res, User{
+					UserId:   userData["userId"],
+					Gid:      userData["gid"],
+					Pf:       userData["pf"],
+					NickName: userData["nickName"],
+					Head:     userData["head"],
+					OpenId:   openId,
+					InBlack:  inBlack,
+					Option:   option["option"],
+				})
+			}
+
+		}
+
+		response.Success(c, gin.H{
+			"data":  res,
+			"count": len(res),
+		})
 		return
+	} else {
+		data, _ = global.App.Redis.ZRevRangeWithScores(context.Background(), userTotalKey, int64(form.Offset), int64(form.Offset+form.Limit)-1).Result()
+
 	}
 
-	//读取黑名单
-	blackListKey := config.Get("app.black_list_table")
-	black, err := global.App.Redis.ZRange(context.Background(), blackListKey, 0, -1).Result()
+	count, err := global.App.Redis.ZCard(context.Background(), userTotalKey).Result()
 	if err != nil {
-		response.Fail(c, 1003, "读取黑名单列表失败"+err.Error())
+		response.Fail(c, 1001, err.Error())
 		return
 	}
 
-	var res []User
 	for _, val := range data {
 		userKey := val.Member.(string)
 		userData, err := global.App.Redis.HGetAll(context.Background(), userKey).Result()

+ 25 - 53
controller/v1/userAds.go

@@ -5,7 +5,6 @@ import (
 	"designs/app/common/response"
 	"designs/global"
 	"designs/model"
-	"designs/utils"
 	"github.com/gin-gonic/gin"
 	"math"
 	"strconv"
@@ -23,17 +22,14 @@ func UserAdsOverview(c *gin.Context) {
 	//近七日广告总数
 	//查询近七日活跃总数
 	now := time.Now()
-	sevenDayAgo := now.AddDate(0, 0, -7)
-	thirtyDayAgo := now.AddDate(0, 0, -30)
+	sevenDayAgo := now.AddDate(0, 0, -7).Format("20060102")
+	thirtyDayAgo := now.AddDate(0, 0, -30).Format("20060102")
 
 	var adsCount7 int64
-	err := global.App.DB.Table("user_see_ads").
+	err := global.App.DB.Table("summary_user_see_ads").
 		Where("gid", form.Gid).
-		WhereIn("pf", form.Pf).
-		Where("adsState", 2). //只统计看完的
-		Where("createdAt", ">=", sevenDayAgo).
-		Where("createdAt", "<=", now).
-		Count(&adsCount7).Error
+		Where("pf", form.Pf).
+		Where("date", ">=", sevenDayAgo).Select("SUM(count) as total").Scan(&adsCount7).Error
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
@@ -41,13 +37,10 @@ func UserAdsOverview(c *gin.Context) {
 
 	//近30天广告总数
 	var adsCount30 int64
-	err = global.App.DB.Table("user_see_ads").
+	err = global.App.DB.Table("summary_user_see_ads").
 		Where("gid", form.Gid).
-		WhereIn("pf", form.Pf).
-		Where("adsState", 2). //只统计看完的
-		Where("createdAt", ">=", thirtyDayAgo).
-		Where("createdAt", "<=", now).
-		Count(&adsCount30).Error
+		Where("pf", form.Pf).
+		Where("date", ">=", thirtyDayAgo).Select("SUM(count) as total").Scan(&adsCount30).Error
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
@@ -77,10 +70,10 @@ func UserAdsOverview(c *gin.Context) {
 		return
 	}
 	var adsTotal int64
-	err = global.App.DB.Table("user_see_ads").
+	err = global.App.DB.Table("summary_user_see_ads").
 		Where("gid", form.Gid).
-		Where("adsState", 2). //只统计看完的
-		WhereIn("pf", form.Pf).Count(&adsTotal).Error
+		Where("pf", form.Pf).
+		Select("SUM(count) as total").Scan(&adsTotal).Error
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
@@ -120,54 +113,33 @@ func UserAdsDaily(c *gin.Context) {
 		EndTime   string   `form:"endTime" json:"endTime" binding:"required"`
 	}{})
 
-	dates := utils.GetTimeDayDate(form.StartTime, form.EndTime)
-	//response.Success(c, gin.H{
-	//	"dates": dates,
-	//})
-	//return
-
-	form.EndTime = form.EndTime + " 23:59:59"
-
-	//近七日广告总数
-	var userAdsDaily []struct {
-		Date  string `json:"date" gorm:"not null;column:date;"`
-		Count int64  `json:"count" gorm:"not null;column:count;"`
-	}
+	startDate := strings.Replace(form.StartTime, "-", "", -1)
+	endDate := strings.Replace(form.EndTime, "-", "", -1)
 
-	err := global.App.DB.Table("user_see_ads").
+	var summary []model.SummaryUserSeeAds
+	err := global.App.DB.Table("summary_user_see_ads").
 		Where("gid", form.Gid).
-		WhereIn("pf", form.Pf).
-		Where("adsState", 2). //只统计看完的
-		Where("createdAt", ">=", form.StartTime).
-		Where("createdAt", "<=", form.EndTime).
-		Group("date").
-		Select("count(*) as count", "date").
-		Scan(&userAdsDaily).Error
+		Where("pf", form.Pf).
+		Where("date", ">=", startDate).
+		Where("date", "<=", endDate).Scan(&summary).Error
+
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
 
-	res := make(map[string]interface{})
-	for k := range dates {
-		res[k] = 0
-	}
-
-	for _, v := range userAdsDaily {
-		date := v.Date[0:4] + "-" + v.Date[4:6] + "-" + v.Date[6:]
-		res[date] = v.Count
-	}
+	res := make(map[string]int)
+	for _, v := range summary {
+		newDate := v.Date[:4] + "-" + v.Date[4:6] + "-" + v.Date[6:]
 
-	var count int64
-	for _, v := range userAdsDaily {
-		count = count + v.Count
+		res[newDate] = v.Count
 	}
 
 	response.Success(c, gin.H{
 		"data": map[string]interface{}{
 			"userAdsDaily": res,
-			"count":        count,
-			"avg":          count / int64(len(dates)),
+			"count":        0,
+			"avg":          0,
 		},
 	})
 

+ 183 - 16
controller/v1/userBehavior.go

@@ -1,10 +1,13 @@
 package v1
 
 import (
+	"bufio"
 	"context"
 	"designs/app/common/request"
 	"designs/app/common/response"
+	"designs/config"
 	"designs/global"
+	"designs/model"
 	"designs/service"
 	"designs/utils"
 	"encoding/json"
@@ -14,6 +17,8 @@ import (
 	"go.mongodb.org/mongo-driver/v2/mongo/options"
 	"math"
 	"math/big"
+	"os"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
@@ -26,6 +31,7 @@ func Summary(c *gin.Context) {
 		Pf  string `form:"pf" json:"pf" binding:"required"`
 	}{})
 
+	start := time.Now()
 	//查询用户总数
 	var userCount int64
 	err := global.App.DB.Table("user").
@@ -36,7 +42,7 @@ func Summary(c *gin.Context) {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
-
+	fmt.Println(time.Since(start))
 	//查询近七日活跃总数
 	now := time.Now()
 	sevenDayAgo := now.AddDate(0, 0, -7)
@@ -54,6 +60,7 @@ func Summary(c *gin.Context) {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
 
 	var activeUserCount30 int64
 	err = global.App.DB.Table("user_login").
@@ -67,6 +74,7 @@ func Summary(c *gin.Context) {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
 
 	//从redis中读取缓存
 	var avgTimeString string
@@ -243,6 +251,7 @@ func UserTrendsOverview(c *gin.Context) {
 		EndTime   string `form:"endTime" json:"endTime" binding:"required"`
 	}{})
 
+	start := time.Now()
 	//查询用户新增
 	var registerCount int64
 	err := global.App.DB.Table("user").
@@ -255,19 +264,20 @@ func UserTrendsOverview(c *gin.Context) {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
-
+	fmt.Println(time.Since(start))
 	//查询活跃设备
 	var activeCount int64
-	err = global.App.DB.Table("user_online").
-		Where("gid", form.Gid).
-		Where("pf", form.Pf).
-		Where("logTime", ">=", form.StartTime).
-		Where("logTime", "<=", form.EndTime).
-		Distinct("userId").Count(&activeCount).Error
-	if err != nil {
-		response.Fail(c, 1001, err.Error())
-		return
+	//sql := "SELECT COUNT(DISTINCT(`userId`)) FROM `user_online` USE INDEX(date) WHERE gid = ? AND pf = ? AND date >= ? AND date <= ?"
+	//err = global.App.DB.Raw(sql, form.Gid, form.Pf, strings.Replace(form.StartTime, "-", "", 5), strings.Replace(form.EndTime, "-", "", 5)).Count(&activeCount).Error
+
+	UserLoginBydDay := utils.GetTimeDayDate(form.StartTime, form.EndTime) //用户分别是在哪天注册的
+	var activeLog [][]int
+	//逐天读取每天活跃的用户数据
+	for k := range UserLoginBydDay {
+		activeLog = append(activeLog, service.GetLocalActiveLog(form.Gid, form.Pf, k))
 	}
+	activeLogCount := utils.UnionOfSubArrays(activeLog)
+	activeCount = int64(len(activeLogCount))
 
 	//查询启动次数
 	var loginCount int64
@@ -281,6 +291,7 @@ func UserTrendsOverview(c *gin.Context) {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
 	//查询平均启动时长
 	//查询活跃用户月趋势图
 	_, _, activeTime, err := service.GetActiveMouthDistribution(form.Pf, form.Gid, form.StartTime, form.EndTime)
@@ -408,36 +419,45 @@ func DataTradesDetail(c *gin.Context) {
 	}
 	var data = make(map[string]dayData)
 
+	start := time.Now()
 	//查询新增设备趋势图
 	NewUser, _, _, err := service.GetRegisterDayDistribution(form.Pf, form.Gid, form.StartTime, form.EndTime)
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
 	//查询活跃用户趋势图
 	ActiveUser, _, _, err := service.GetActiveDayDistribution(form.Pf, form.Gid, form.StartTime, form.EndTime)
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
+
 	//查询活跃用户周趋势图
 	ActiveUserWeek, _, _, err := service.GetActiveWeekDistribution(form.Pf, form.Gid, form.StartTime, form.EndTime)
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
+
 	//查询活跃用户月趋势图
 	ActiveUserMouth, _, _, err := service.GetActiveMouthDistribution(form.Pf, form.Gid, form.StartTime, form.EndTime)
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
+
 	//查询启动次数
 	ActiveStart, _, _, err := service.GetActiveMouthDistribution(form.Pf, form.Gid, form.StartTime, form.EndTime)
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
 	}
+	fmt.Println(time.Since(start))
 
 	//查询平均启动时间
 	AvgTime, _, _, err := service.UserOnlineSummaryByDay(form.Gid, form.Pf, form.StartTime, form.EndTime)
@@ -462,6 +482,18 @@ func DataTradesDetail(c *gin.Context) {
 
 }
 
+func OnlineToFile(c *gin.Context) {
+	service.OnlineToFile()
+
+	//now := time.Now()
+	//for i := 1; i <= 60; i++ {
+	//	lastDay := now.AddDate(0, 0, -i).Format("20060102")
+	//	crons.AdsDataSummary(lastDay)
+	//}
+
+	response.Success(c, gin.H{})
+}
+
 func RemainDataBydDay(c *gin.Context) {
 	form := request.Check(c, &struct {
 		Gid       string `form:"gid" json:"gid" binding:"required"`
@@ -471,7 +503,13 @@ func RemainDataBydDay(c *gin.Context) {
 		Type      int    `form:"type" json:"type" binding:"required"`
 	}{})
 
-	data, err := service.RemainDataBydDay(form.Type, form.Pf, form.Gid, form.StartTime, form.EndTime)
+	//data, err := service.RemainDataBydDay(form.Type, form.Pf, form.Gid, form.StartTime, form.EndTime)
+	//if err != nil {
+	//	response.Fail(c, 1001, err.Error())
+	//	return
+	//}
+
+	data, err := service.RemainDataBydDayNew(form.Type, form.Pf, form.Gid, form.StartTime, form.EndTime)
 	if err != nil {
 		response.Fail(c, 1001, err.Error())
 		return
@@ -1092,8 +1130,8 @@ func SetGameCondition(c *gin.Context) {
 func GameConditionList(c *gin.Context) {
 	form := request.Check(c, &struct {
 		Gid    string `form:"gid" json:"gid" binding:"required"`
-		Pid    int64  `form:"pid" json:"pid" binding:""`
-		Aid    int64  `form:"aid" json:"aid" binding:""`
+		Pid    string `form:"pid" json:"pid" binding:""`
+		Aid    string `form:"aid" json:"aid" binding:""`
 		Type   string `form:"type" json:"type" binding:""`
 		Offset int    `form:"offset" json:"offset" binding:""`
 		Limit  int    `form:"limit" json:"limit" binding:"required"`
@@ -1105,10 +1143,10 @@ func GameConditionList(c *gin.Context) {
 	if form.Type != "" {
 		filter["type"] = form.Type
 	}
-	if form.Pid != 0 {
+	if form.Pid != "" {
 		filter["pid"] = form.Pid
 	}
-	if form.Aid != 0 {
+	if form.Aid != "" {
 		filter["aid"] = form.Aid
 	}
 
@@ -1393,3 +1431,132 @@ func BehaviorListCake(c *gin.Context) {
 	})
 
 }
+
+func SplitOnlineData(c *gin.Context) {
+
+	//只保留最近30天的数据
+	now := time.Now()
+	for i := 0; i <= 29; i++ {
+		date := now.AddDate(0, 0, -i).Format("2006-01-02")
+		date1 := now.AddDate(0, 0, -i).Format("20060102")
+
+		var dir string
+		if config.Get("app.local") == "local" {
+			//url = "mongodb://localhost:27017"
+			dir = "storage"
+		} else {
+			dir = "/www/wwwroot/chunhao_receive/storage"
+		}
+
+		//读取对应的文件夹
+		dirPath := filepath.Join(dir, date)
+		dateDir, _ := os.ReadDir(dirPath)
+		fmt.Println(dateDir, date)
+
+		for _, v := range dateDir {
+			var onlineData []model.UserOnlineSplit
+
+			fileName := v.Name()
+
+			fileNameSplit := strings.Split(fileName, "_")
+
+			if len(fileNameSplit) < 2 {
+				continue
+			}
+
+			last := fileNameSplit[len(fileNameSplit)-1]
+			pf := last[:len(last)-4]
+
+			gid := strings.TrimRight(fileName, "_"+last)
+
+			fmt.Println("fileName:", fileName)
+			fmt.Println("gid:", gid)
+			fmt.Println("last:", last)
+
+			//err1 := DropTable(gid, date1)
+			//fmt.Println(err1)
+			//continue
+
+			filePath := filepath.Join(dirPath, fileName)
+			file, _ := os.Open(filePath)
+
+			// 创建 Scanner 对象
+			scanner := bufio.NewScanner(file)
+			// 逐行读取文件内容
+			lineNumber := 1
+			for scanner.Scan() {
+				line := scanner.Text() // 获取当前行内容
+				lineNumber++
+				lineData := strings.Split(line, ",")
+
+				userId, _ := strconv.Atoi(lineData[0])
+				types, _ := strconv.Atoi(lineData[1])
+
+				loc, _ := time.LoadLocation("Asia/Shanghai")
+				LogTime, _ := time.ParseInLocation("2006-01-02 15:04:05", lineData[2], loc)
+
+				//fmt.Println(LogTime, lineData[2])
+				onlineData = append(onlineData, model.UserOnlineSplit{
+					Pf:      pf,
+					UserId:  userId,
+					Type:    types,
+					LogTime: LogTime,
+				})
+			}
+			file.Close() // 确保函数结束时关闭文件
+
+			//数据存入mysql
+			err := InsertOnlineSpilt(gid, date1, onlineData)
+			if err != nil {
+				fmt.Println("插入数据库错误", err)
+			}
+
+		}
+
+		fmt.Println(date, "数据归档数据库完成,耗时:", time.Since(now))
+	}
+}
+
+func InsertOnlineSpilt(gid string, date string, online []model.UserOnlineSplit) error {
+
+	//先根据date ,gid ,判定存哪个表
+	tableName := "user_online" + "_" + date + "_" + gid
+
+	err := global.App.DB.Exec(`
+    		CREATE TABLE IF NOT EXISTS ` + tableName + ` (
+			  id int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+			  pf varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '登录路径',
+			  userId int NOT NULL COMMENT '用户ID',
+			  type tinyint NOT NULL COMMENT '1:在线 2.下线',
+			  logTime timestamp NULL DEFAULT NULL COMMENT '时间',
+			  PRIMARY KEY (id) USING BTREE,
+				KEY pf (pf)
+			) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
+		`).Error
+	if err != nil {
+		fmt.Println("创建数据库失败", tableName, err.Error())
+		return err
+	}
+
+	err = global.App.DB.Table(tableName).CreateInBatches(&online, 1000).Error
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func DropTable(gid string, date string) error {
+
+	//先根据date ,gid ,判定存哪个表
+	tableName := "user_online" + "_" + date + "_" + gid
+
+	sql := "DROP TABLE IF EXISTS " + tableName
+	err := global.App.DB.Exec(sql).Error
+	if err != nil {
+		fmt.Println("创建数据库失败", tableName, err.Error())
+		return err
+	}
+
+	return nil
+}

+ 62 - 0
controller/v1/wxGameData.go

@@ -0,0 +1,62 @@
+package v1
+
+import (
+	"context"
+	"designs/global"
+	"designs/service"
+	"encoding/json"
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	"net/url"
+	"time"
+)
+
+// 获取微信的accessCode
+func GetAccessToken(gid string, ttAppid string, ttSecret string) (string, error) {
+	tokenKey := gid + "||" + "wxToken"
+
+	AccessToken := global.App.Redis.Get(context.Background(), tokenKey).Val()
+	if AccessToken == "" {
+
+		params := url.Values{
+			"appid":      {ttAppid},
+			"secret":     {ttSecret},
+			"grant_type": {"client_credential"},
+		}
+
+		//请求接口获取token
+		content, err := service.CurlGet("https://api.weixin.qq.com/cgi-bin/token", params, nil)
+		if err != nil {
+			return "", err
+		}
+		var resp struct {
+			AccessToken string `json:"access_token"`
+			ExpiresIn   int    `json:"expires_in"`
+			ExpiresAt   int    `json:"expiresAt"`
+			Errcode     int    `json:"errcode"`
+			Errmsg      string `json:"errmsg"`
+		}
+
+		str2oErr := json.Unmarshal([]byte(content), &resp)
+		if str2oErr != nil {
+			return "", err
+		}
+
+		if resp.Errcode != 0 {
+			return "", errors.New(resp.Errmsg)
+		}
+
+		AccessToken = resp.AccessToken
+		global.App.Redis.Set(context.Background(), tokenKey, AccessToken, time.Second*30)
+	}
+
+	return AccessToken, nil
+}
+
+// 获取微信的当日数据
+func GetDaily(c *gin.Context) {
+	//form := request.Check(c, &struct {
+	//
+	//}{})
+
+}

+ 133 - 0
crons/userAction.go

@@ -0,0 +1,133 @@
+package crons
+
+import (
+	"designs/global"
+	"designs/model"
+	"fmt"
+	"os"
+	"time"
+)
+
+func ActiveDelete() {
+	//读取前一日数据
+	now := time.Now()
+
+	for i := 30; i <= 40; i++ {
+		last := now.AddDate(0, 0, -i).Format("2006-01-02")
+
+		path := "storage" + "/" + last
+
+		//删除前一天的文件夹
+		err := os.RemoveAll(path)
+		if err != nil {
+			fmt.Println("删除文件夹失败:"+path, err)
+		} else {
+			fmt.Println("删除文件夹完成:" + path)
+		}
+	}
+}
+
+func OnlineDatabaseDelete() {
+	//获取到表名
+	now := time.Now()
+
+	date := now.AddDate(0, 0, -29).Format("20060102")
+
+	tableName := "user_online_" + date
+
+	var tableList []string
+	sql := "SELECT table_name FROM information_schema.tables WHERE table_schema = 'chunhao' AND table_name LIKE '" + tableName + "%'"
+	err := global.App.DB.Raw(sql).Pluck("table_name", &tableList).Error
+
+	if err != nil {
+		global.App.Log.Error("查询", date, "user_online数据表失败", err.Error())
+		return
+	}
+
+	//批量删除数据表
+	for _, table := range tableList {
+		sql := "drop table IF EXISTS " + table
+		err := global.App.DB.Exec(sql).Error
+		if err != nil {
+			global.App.Log.Error("删除", table, "数据表失败", err.Error())
+			return
+		}
+	}
+
+	global.App.Log.Info(date, "数据表清理完成", tableList)
+}
+
+//// 对留存数据进行汇算
+//func RemainDataSummary() {
+//	//计算上一日的留存
+//	lastDay := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
+//
+//	//读取到所有的gid ,根据GID计算这些数据
+//
+//
+//}
+
+//对广告数据进行汇算
+
+func AdsDataSummary() {
+	lastDay := time.Now().AddDate(0, 0, -1).Format("20060102")
+
+	//计算出上一日的广告数据汇总
+	var adsSummary []struct {
+		Count    int    `json:"count" gorm:"column:count"`
+		Pf       string `json:"pf" gorm:"column:pf"`
+		Gid      string `json:"gid" gorm:"column:gid"`
+		SumType1 string `json:"sumType1" gorm:"column:sumType1"`
+		SumType2 string `json:"sumType2" gorm:"column:sumType2"`
+		SumType0 string `json:"sumType0" gorm:"column:sumType0"`
+	}
+
+	err := global.App.DB.Table("user_see_ads").
+		Where("date", lastDay).
+		Group("gid,pf").
+		Select("count(*) as count", "pf", "gid",
+			"SUM(adsState = 1) AS sumType1",
+			"SUM(adsState = 2) AS sumType2",
+			"SUM(adsState = 0) AS sumType0").
+		Scan(&adsSummary).Error
+
+	fmt.Println(adsSummary)
+	if err != nil {
+		global.App.Log.Error("查询广告汇总数据报错:", err)
+		return
+	}
+	var SummaryUserSeeAds []model.SummaryUserSeeAds
+
+	now := model.XTime{
+		Time: time.Now(),
+	}
+	for _, summary := range adsSummary {
+		SummaryUserSeeAds = append(SummaryUserSeeAds, model.SummaryUserSeeAds{
+			Date:      lastDay,
+			Count:     summary.Count,
+			Pf:        summary.Pf,
+			Gid:       summary.Gid,
+			SumType1:  summary.SumType1,
+			SumType2:  summary.SumType2,
+			SumType0:  summary.SumType0,
+			CreatedAt: now,
+		})
+	}
+
+	//将汇总数据存入数据库中
+	err = global.App.DB.Table("summary_user_see_ads").CreateInBatches(&SummaryUserSeeAds, 100).Error
+	if err != nil {
+		global.App.Log.Error("存入统计数据失败", err.Error())
+		fmt.Println(err.Error())
+		return
+	}
+
+	return
+}
+
+//// 对打点数据进行汇算
+//func ActionDataSummary() {
+//	lastDay := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
+//
+//	//
+//}

+ 2 - 1
global/app.go

@@ -3,6 +3,7 @@ package global
 import (
 	"designs/utils"
 	"github.com/go-redis/redis/v8"
+	"github.com/robfig/cron/v3"
 	"go.mongodb.org/mongo-driver/v2/mongo"
 	"go.uber.org/zap"
 	"io"
@@ -22,7 +23,7 @@ type Application struct {
 	DB *utils.WtDB
 
 	Redis *redis.Client
-	//Cron      *cron.Cron
+	Cron  *cron.Cron
 	//Machinery *machinery.Server
 
 	//日志

+ 2 - 1
go.mod

@@ -13,7 +13,9 @@ require (
 	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
 	github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6
 	github.com/pkg/errors v0.9.1
+	github.com/robfig/cron/v3 v3.0.0
 	github.com/sirupsen/logrus v1.9.3
+	github.com/tencentyun/cos-go-sdk-v5 v0.7.65
 	go.mongodb.org/mongo-driver/v2 v2.0.0-beta2
 	go.uber.org/zap v1.27.0
 	golang.org/x/crypto v0.27.0
@@ -26,7 +28,6 @@ require (
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mozillazg/go-httpheader v0.4.0 // indirect
-	github.com/tencentyun/cos-go-sdk-v5 v0.7.65 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 )
 

+ 3 - 0
main.go

@@ -66,6 +66,9 @@ func main() {
 		}
 	}()
 
+	//初始化计划任务
+	bootstrap.InitializeCron()
+
 	bootstrap.RunServer()
 	// ginServer.RunTLS(":443", "your_certificate.crt", "your_private_key.key")
 }

+ 9 - 0
middleware/isSuper.go

@@ -0,0 +1,9 @@
+package middleware
+
+import "github.com/gin-gonic/gin"
+
+func CheckSuper() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		//
+	}
+}

+ 10 - 0
model/file.go

@@ -5,6 +5,16 @@ type File struct {
 	FileName  string `json:"file_name" gorm:"column:fileName;"`
 	LocalPath string `json:"local_path" gorm:"column:localPath;"`
 	CosPath   string `json:"cos_path" gorm:"column:cosPath;"`
+	Type      int    `json:"type" gorm:"column:type;"`
+	Dir       string `json:"dir" gorm:"column:dir;"`
 	CreatedAt XTime  `json:"createdAt" gorm:"column:createdAt; "`
 	UpdatedAt XTime  `json:"updatedAt" gorm:"column:updatedAt; "`
 }
+
+type AdsDataSummary struct {
+	Id    int    `json:"id" gorm:"primary_key"`
+	Gid   string `json:"gid" gorm:"column:gid;"`
+	Pf    string `json:"pf" gorm:"column:pf;"`
+	Date  string `json:"date" gorm:"column:date;"`
+	Count int    `json:"count" gorm:"column:count;"`
+}

+ 13 - 0
model/summary.go

@@ -0,0 +1,13 @@
+package model
+
+type SummaryUserSeeAds struct {
+	Id        int    `json:"id" gorm:"primary_key;"`
+	Date      string `json:"date" gorm:"column:date;"`
+	Count     int    `json:"count" gorm:"column:count"`
+	Pf        string `json:"pf" gorm:"column:pf"`
+	Gid       string `json:"gid" gorm:"column:gid"`
+	SumType1  string `json:"sumType1" gorm:"column:sumType1"`
+	SumType2  string `json:"sumType2" gorm:"column:sumType2"`
+	SumType0  string `json:"sumType0" gorm:"column:sumType0"`
+	CreatedAt XTime  `json:"createdAt" gorm:"column:createdAt;type:date;"`
+}

+ 16 - 6
model/user.go

@@ -45,18 +45,28 @@ type UserOnline struct {
 	ID      int       `json:"id" gorm:"not null;"`
 	Pf      string    `json:"pf" gorm:"not null;"`
 	Gid     string    `json:"gid" gorm:"not null;"`
+	UserId  int       `json:"userId" gorm:"not null;column:userId;"`
+	Type    int       `json:"type" gorm:"not null;"`
+	Date    string    `json:"date" gorm:"not null;"`
+	LogTime time.Time `json:"logTime" gorm:"column:logTime;"`
+}
+
+type UserOnlineSplit struct {
+	ID      int       `json:"id" gorm:"not null;"`
+	Pf      string    `json:"pf" gorm:"not null;"`
 	Type    int       `json:"type" gorm:"not null;"`
 	UserId  int       `json:"userId" gorm:"not null;column:userId;"`
 	LogTime time.Time `json:"logTime" gorm:"column:logTime;"`
 }
 
 type Admin struct {
-	ID        int       `json:"id" gorm:"not null;"`
-	Account   string    `json:"account" gorm:"not null;"`
-	Password  string    `json:"password" gorm:"not null;"`
-	Name      string    `json:"name" gorm:"not null;"`
-	CreatedAt time.Time `json:"createdAt" gorm:"column:createdAt;"`
-	UpdatedAt time.Time `json:"updatedAt" gorm:"column:updatedAt;"`
+	ID         int    `json:"id" gorm:"not null;"`
+	Account    string `json:"account" gorm:"not null;"`
+	Password   string `json:"password" gorm:"not null;"`
+	Name       string `json:"name" gorm:"not null;"`
+	CreatedAt  XTime  `json:"createdAt" gorm:"column:createdAt;"`
+	UpdatedAt  XTime  `json:"updatedAt" gorm:"column:updatedAt;"`
+	Permission string `json:"permission" gorm:""`
 }
 
 type UserSeeAds struct {

+ 17 - 6
route/api.go

@@ -16,6 +16,7 @@ func SetApiGroupRoutes(router *gin.RouterGroup) {
 
 	router.GET("/download", v1.DownloadFile) //文件下载
 	router.POST("/upload", v1.Upload)
+	router.POST("/uploadToCos", v1.UploadToCos)
 
 	GroupV1 := router.Group("")
 	GroupV1.Use(middleware.TokenAuthMiddleware()).Use()
@@ -24,6 +25,7 @@ func SetApiGroupRoutes(router *gin.RouterGroup) {
 
 		GroupV1.POST("/user/addGidConfig", v1.AddGidConfig)
 		GroupV1.POST("/user/getGidConfig", v1.GetGidConfig)
+		GroupV1.POST("/user/gidList", v1.GidList)
 
 		GroupV1.POST("/user/addUserToBlackList", v1.AddUserToBlackList)
 		GroupV1.POST("/user/deleteUserToBlackList", v1.DeleteUserFormBlackList)
@@ -51,6 +53,7 @@ func SetApiGroupRoutes(router *gin.RouterGroup) {
 		//游戏自定义事件管理
 		GroupV1.POST("/user/setGameAction", v1.SetGameAction)
 		GroupV1.POST("/user/updateGameAction", v1.UpdateGameAction)
+		GroupV1.POST("/user/copyGameAction", v1.CopyGameAction)
 		GroupV1.POST("/user/updateGameActionOption", v1.UpdateGameActionOption)
 		GroupV1.POST("/user/addGameActionOption", v1.AddGameActionOption)
 		GroupV1.POST("/user/deleteGameActionOption", v1.DeleteGameActionOption)
@@ -77,11 +80,18 @@ func SetApiGroupRoutes(router *gin.RouterGroup) {
 		GroupV1.POST("/user/userAdsCake", v1.UserAdsCake)
 		GroupV1.POST("/user/behaviorListCake", v1.BehaviorListCake)
 
-		//文件上传l
-		router.POST("/file/localFileToService", v1.LocalFileToService) //
-		router.POST("/file/fileList", v1.FileList)                     //
-		router.POST("/file/fileDelete", v1.FileDelete)                 //
-
+		//文件上传
+		GroupV1.POST("/file/localFileToService", v1.LocalFileToService)
+		GroupV1.POST("/file/fileList", v1.FileList)
+		GroupV1.POST("/file/fileDelete", v1.FileDelete)
+		GroupV1.POST("/file/createDir", v1.CreateDir)
+		GroupV1.POST("/file/dirDownload", v1.DirDownload)
+
+		//管理员管理
+		GroupV1.POST("/admin/adminList", v1.AdminList)
+		GroupV1.POST("/admin/setAdmin", v1.SetAdmin)
+		GroupV1.POST("/admin/deleteAdmin", v1.DeleteAdmin)
+		GroupV1.POST("/admin/updateAdmin", v1.UpdateAdmin)
 	}
 
 	router.POST("/SetUserBehaviorBak", v1.SetUserBehaviorBak)       //测试接口
@@ -91,7 +101,8 @@ func SetApiGroupRoutes(router *gin.RouterGroup) {
 	router.POST("/Websocket", v1.Websocket)                         //测试接口
 	router.POST("/Websocket1", v1.Websocket1)                       //测试接口
 	router.POST("/Websocket2", v1.Websocket2)                       //测试接口
-
+	router.POST("/OnlineToFile", v1.OnlineToFile)                   //测试接口
+	router.POST("/SplitOnlineData", v1.SplitOnlineData)             //迁移数据到online拆分表
 	router.POST("/InitGidConfig", v1.InitGidConfig)
 
 }

+ 148 - 40
service/UserOnlineService.go

@@ -1,10 +1,15 @@
 package service
 
 import (
-	"designs/global"
-	"designs/model"
+	"bufio"
+	"designs/config"
 	"designs/utils"
+	"fmt"
 	"math"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
 	"time"
 )
 
@@ -15,28 +20,41 @@ type logData struct {
 
 // 查询单日使用时长曲线
 func UserOnlineSummaryByDay(gid string, pf string, startTime string, endTime string) (map[string]int, int, int, error) {
-	var onlineData []model.UserOnline
-	//从数据库中查询出所有的数据
-	query := global.App.DB.Table("user_online").Where("gid", gid).Where("pf", pf)
+	//var onlineData []model.UserOnline
+	////从数据库中查询出所有的数据
+	//query := global.App.DB.Table("user_online").Where("gid", gid).Where("pf", pf)
+	//
+	//if startTime != "" && endTime != "" {
+	//	query = query.Where("logTime", ">=", startTime).Where("logTime", "<=", endTime)
+	//}
+	//err := query.Scan(&onlineData).Error
+	//if err != nil {
+	//	return nil, 0, 0, err
+	//}
+	////对数据进行分组
+	//userOnlineData := make(map[string]map[int][]logData)
+	//dateSlice := utils.GetTimeDayDateFormat(startTime, endTime)
+	//for _, v := range dateSlice {
+	//	userOnlineData[v] = make(map[int][]logData)
+	//}
+	//
+	//for _, v := range onlineData {
+	//	date := v.LogTime.Format("2006-01-02")
+	//	userOnlineData[date][v.UserId] = append(userOnlineData[date][v.UserId], logData{v.LogTime, v.Type})
+	//}
 
-	if startTime != "" && endTime != "" {
-		query = query.Where("logTime", ">=", startTime).Where("logTime", "<=", endTime)
-	}
-	err := query.Scan(&onlineData).Error
-	if err != nil {
-		return nil, 0, 0, err
-	}
-	//对数据进行分组
 	userOnlineData := make(map[string]map[int][]logData)
+
 	dateSlice := utils.GetTimeDayDateFormat(startTime, endTime)
+
 	for _, v := range dateSlice {
+
 		userOnlineData[v] = make(map[int][]logData)
-	}
 
-	for _, v := range onlineData {
-		date := v.LogTime.Format("2006-01-02")
-		userOnlineData[date][v.UserId] = append(userOnlineData[date][v.UserId], logData{v.LogTime, v.Type})
+		userOnlineData[v] = GetLocalActiveLogDetail(gid, pf, v)
+
 	}
+
 	//计算每天的数据,然后得出平均值
 	var totalAvg int
 	var total int
@@ -73,32 +91,36 @@ func UserOnlineSummaryByDay(gid string, pf string, startTime string, endTime str
 }
 
 func UserOnlineSummary(gid string, pf string, date string, startTime string, endTime string) (map[int]int64, error) {
-	var onlineData []model.UserOnline
-	//从数据库中查询出所有的数据
-	query := global.App.DB.Table("user_online").Where("gid", gid).Where("pf", pf)
-	if date != "" {
-		query = query.Where("date", "=", date)
-	}
+	//var onlineData []model.UserOnline
+	////从数据库中查询出所有的数据
+	//query := global.App.DB.Table("user_online").Where("gid", gid).Where("pf", pf)
+	//if date != "" {
+	//	query = query.Where("date", "=", date)
+	//}
+	//
+	//if startTime != "" && endTime != "" {
+	//	query = query.Where("logTime", ">=", startTime).Where("logTime", "<=", endTime)
+	//}
+	//err := query.Scan(&onlineData).Error
+	//if err != nil {
+	//	return nil, err
+	//}
 
-	if startTime != "" && endTime != "" {
-		query = query.Where("logTime", ">=", startTime).Where("logTime", "<=", endTime)
-	}
-	err := query.Scan(&onlineData).Error
-	if err != nil {
-		return nil, err
-	}
+	////对数据进行分组
+	//userOnlineData := make(map[int][]logData)
+	//for _, v := range onlineData {
+	//	value, exists := userOnlineData[v.UserId]
+	//	if exists {
+	//		userOnlineData[v.UserId] = append(value, logData{v.LogTime, v.Type})
+	//
+	//	} else {
+	//		userOnlineData[v.UserId] = []logData{{v.LogTime, v.Type}}
+	//	}
+	//}
+	//onlineData = nil
 
-	//对数据进行分组
 	userOnlineData := make(map[int][]logData)
-	for _, v := range onlineData {
-		value, exists := userOnlineData[v.UserId]
-		if exists {
-			userOnlineData[v.UserId] = append(value, logData{v.LogTime, v.Type})
-		} else {
-			userOnlineData[v.UserId] = []logData{{v.LogTime, v.Type}}
-		}
-	}
-	onlineData = nil
+	userOnlineData = GetLocalActiveLogDetailRange(gid, pf, startTime, endTime)
 
 	userLogMsg := make(map[int]int64)
 	//分组后对于每个用户的数据进行处理,得到他们的具体在线时长
@@ -152,3 +174,89 @@ func calculateUserOnlineTime(logData []logData) int64 {
 
 	return onlineTimeTotal
 }
+
+func GetLocalActiveLogDetail(gid string, pf string, date string) map[int][]logData {
+	var dir string
+	if config.Get("app.local") == "local" {
+		//url = "mongodb://localhost:27017"
+		dir = "storage"
+	} else {
+		dir = "/www/wwwroot/chunhao_receive/storage"
+	}
+
+	dirPath := filepath.Join(dir, date)
+	fileName := fmt.Sprintf("%s_%s.txt", gid, pf)
+	filePath := filepath.Join(dirPath, fileName)
+
+	file, _ := os.Open(filePath)
+
+	defer file.Close() // 确保函数结束时关闭文件
+
+	// 创建 Scanner 对象
+	scanner := bufio.NewScanner(file)
+
+	res := make(map[int][]logData)
+	// 逐行读取文件内容
+	lineNumber := 1
+	for scanner.Scan() {
+		line := scanner.Text() // 获取当前行内容
+		lineNumber++
+
+		//11887,2,2025-03-24 09:05:19
+		lineSlice := strings.Split(line, ",")
+
+		userId, _ := strconv.Atoi(lineSlice[0])
+		types, _ := strconv.Atoi(lineSlice[1])
+		// 解析时间
+		t, _ := time.Parse("2006-01-02  15:04:05", lineSlice[2])
+
+		res[userId] = append(res[userId], logData{t, types})
+	}
+
+	return res
+}
+
+func GetLocalActiveLogDetailRange(gid string, pf string, start string, end string) map[int][]logData {
+	var dir string
+	if config.Get("app.local") == "local" {
+		//url = "mongodb://localhost:27017"
+		dir = "storage"
+	} else {
+		dir = "/www/wwwroot/chunhao_receive/storage"
+	}
+
+	dateSlice := utils.GetTimeDayDateFormat(start, end)
+
+	res := make(map[int][]logData)
+	for _, v := range dateSlice {
+
+		dirPath := filepath.Join(dir, v)
+		fileName := fmt.Sprintf("%s_%s.txt", gid, pf)
+		filePath := filepath.Join(dirPath, fileName)
+
+		file, _ := os.Open(filePath)
+
+		// 创建 Scanner 对象
+		scanner := bufio.NewScanner(file)
+		// 逐行读取文件内容
+		lineNumber := 1
+		for scanner.Scan() {
+			line := scanner.Text() // 获取当前行内容
+			lineNumber++
+
+			//11887,2,2025-03-24 09:05:19
+			lineSlice := strings.Split(line, ",")
+
+			userId, _ := strconv.Atoi(lineSlice[0])
+			types, _ := strconv.Atoi(lineSlice[1])
+			// 解析时间
+			t, _ := time.Parse("2006-01-02  15:04:05", lineSlice[2])
+
+			res[userId] = append(res[userId], logData{t, types})
+		}
+
+		file.Close() // 确保函数结束时关闭文件
+	}
+
+	return res
+}

+ 69 - 0
service/actionService.go

@@ -1 +1,70 @@
 package service
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"time"
+)
+
+func CurPost(requestUrl string, requestBody interface{}, headerMap map[string]string) (string, error) {
+	//转换json
+	jsonBytes, err := json.Marshal(requestBody)
+	if err != nil {
+		return "", err
+	}
+
+	//创建请求
+	req, err := http.NewRequest("POST", requestUrl, bytes.NewReader(jsonBytes))
+	if err != nil {
+		return "", err
+	}
+	//设置请求头
+	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
+	for k, v := range headerMap {
+		//req.Header.Set("Accept-Encoding", "gzip, deflate, br")
+		req.Header.Set(k, v)
+	}
+	//发送请求
+	clt := http.Client{Timeout: 30 * time.Second}
+	res, err := clt.Do(req)
+	if err != nil {
+		return "", err
+	}
+	//获取结果
+	body, err := io.ReadAll(res.Body)
+	data := string(body)
+
+	return data, err
+
+}
+
+func CurlGet(url string, values url.Values, header http.Header) (string, error) {
+	req, reqErr := http.NewRequest(http.MethodGet, url, nil)
+	if reqErr != nil {
+		return "", reqErr
+	}
+	if header != nil {
+		req.Header = header
+	}
+	if values != nil {
+		req.URL.RawQuery = values.Encode()
+	}
+	fmt.Println(req.URL.Host, req.URL.RawQuery)
+	resp, resErr := http.DefaultClient.Do(req)
+	if resErr != nil {
+		return "", resErr
+	}
+	defer func() { _ = resp.Body.Close() }()
+	content, readErr := io.ReadAll(resp.Body)
+	if readErr != nil {
+		return "", readErr
+	}
+	//if decodeErr := json.Unmarshal(content, result); decodeErr != nil {
+	//	return "", decodeErr
+	//}
+	return string(content), nil
+}

+ 104 - 0
service/option.go

@@ -0,0 +1,104 @@
+package service
+
+//func SetOptionSummary() {
+//
+//	now := time.Now()
+//	//查出当天的所有用户登录数据
+//
+//	fmt.Println(time.Since(now))
+//	userLoginCount := len(userLogin)
+//	//查询出所有的事件选项
+//	var actionOption []struct {
+//		model.UserActionOption
+//		UserId int `json:"userId" gorm:"not null;column:userId;"`
+//	}
+//
+//	err = global.App.DB.Table("user_action_option").
+//		LeftJoin("user_action", "user_action.id = user_action_option.UserActionId").
+//		Where("user_action.actionId", form.ActionId).
+//		Where("user_action.gid", form.Gid).
+//		Where("user_action.createdAt", ">=", form.StartTime).
+//		Where("user_action.createdAt", "<=", form.EndTime).
+//		Select("user_action_option.*", "user_action.userId").
+//		Scan(&actionOption).Error
+//	if err != nil {
+//		response.Fail(c, 1004, err.Error())
+//		return
+//	}
+//	fmt.Println(time.Since(now))
+//	//循环得出选项
+//	optionList := make(map[string]int)
+//	for _, v := range actionOption {
+//		optionList[v.OptionId+"|"+v.Value]++
+//	}
+//
+//	//根据人数进行比对
+//	//计算事件的触发总数和触发用户数
+//	actionSumMap := make(map[string]int)
+//	actionUserSumMap := make(map[string]map[int]bool)
+//
+//	for _, action := range actionOption {
+//		if actionUserSumMap[action.OptionId+"|"+action.Value] == nil {
+//			actionUserSumMap[action.OptionId+"|"+action.Value] = make(map[int]bool)
+//		}
+//		actionSumMap[action.OptionId+"|"+action.Value]++
+//		actionUserSumMap[action.OptionId+"|"+action.Value][action.UserId] = true
+//	}
+//
+//	//根据事件触发和活跃用户数量进行比对得出其他数据
+//	activeUser := make(map[int]bool)
+//	var activeUserSlice []int
+//	for _, users := range userLogin {
+//		activeUser[users.UserId] = true
+//	}
+//	for k := range activeUser {
+//		activeUserSlice = append(activeUserSlice, k)
+//	}
+//
+//	type responses struct {
+//		Id              int     `json:"id"`
+//		ActionId        int     `json:"actionId"`
+//		ActionName      string  `json:"actionName"`
+//		ActionCount     int     `json:"actionCount"`
+//		ActionUserCount int     `json:"actionUserCount"`
+//		ActiveUserRate  float64 `json:"activeUserRate"`
+//		LoginActiveRate float64 `json:"loginActiveRate"`
+//	}
+//
+//	var res []responses
+//
+//	var optionName []model.GameActionOption
+//	optionIdToName := make(map[string]string)
+//	global.App.DB.Table("game_action_option").
+//		LeftJoin("game_action", "game_action.id = game_action_option.actionId").
+//		Where("gid", form.Gid).Scan(&optionName)
+//	for _, v := range optionName {
+//		optionIdToName[v.OptionId] = v.OptionName
+//	}
+//	fmt.Println(time.Since(now))
+//	for k, v := range optionList {
+//		var ActiveUserRate float64
+//		var LoginActiveRate float64
+//		if userLoginCount > 0 {
+//			ActiveUserRate = DivideWithPrecision(actionSumMap[k], userLoginCount)
+//			LoginActiveRate = DivideWithPrecision(len(actionUserSumMap[k]), userLoginCount)
+//		}
+//
+//		optionId := strings.Split(k, "|")[0]
+//		value := strings.Split(k, "|")[1]
+//		valueInt, _ := strconv.Atoi(value)
+//
+//		res = append(res, responses{
+//			ActionId:        valueInt,
+//			ActionName:      optionIdToName[optionId] + ":" + value,
+//			ActionCount:     v,
+//			ActionUserCount: len(actionUserSumMap[k]),
+//			ActiveUserRate:  ActiveUserRate,
+//			LoginActiveRate: LoginActiveRate,
+//		})
+//	}
+//
+//	sort.Slice(res, func(i, j int) bool {
+//		return res[i].ActionId < res[j].ActionId // 正序规则:i的Age小于j时返回true
+//	})
+//}

+ 210 - 8
service/remainData.go

@@ -1,17 +1,26 @@
 package service
 
 import (
+	"bufio"
+	"designs/config"
 	"designs/global"
 	"designs/model"
 	"designs/utils"
+	"fmt"
 	"github.com/pkg/errors"
 	"math"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
 	"time"
 )
 
 func RemainDataBydDay(types int, pf string, gid string, startTime string, endTime string) (map[string]map[string]interface{}, error) {
 	var err error
 
+	start := time.Now()
+
 	//对于传过来的endTime ,做一个处理
 	t, _ := time.Parse("2006-01-02", endTime)
 	endTimeData := t.AddDate(0, 0, 1).Format("2006-01-02")
@@ -21,21 +30,24 @@ func RemainDataBydDay(types int, pf string, gid string, startTime string, endTim
 	var UsersId []int
 	userIdMap := make(map[int]bool)
 	if types == 1 {
+
 		//先计算出这个时间内 ,每天的注册用户数量
 		var users []model.User
 		err = global.App.DB.Table("user").
 			Where("pf", pf).Where("gid", gid).
 			Where("createdAt", ">=", startTime).
 			Where("createdAt", "<=", endTimeData).
+			Select("userId", "createdAt").
 			Scan(&users).Error
 		if err != nil {
 			return nil, err
 		}
 
+		fmt.Printf("步骤2耗时: %v\n", time.Since(start))
+
 		for _, user := range users {
 			userIdMap[user.UserId] = true
 			UsersBydDay[user.CreatedAt.Format("2006-01-02")] = append(UsersBydDay[user.CreatedAt.Format("2006-01-02")], user.UserId)
-
 		}
 		for user := range userIdMap {
 			UsersId = append(UsersId, user)
@@ -63,16 +75,49 @@ func RemainDataBydDay(types int, pf string, gid string, startTime string, endTim
 
 	//把每天的注册用户进行集合,查出后面所有天数的活跃情况
 	var UserLogin []model.UserOnline
-	err = global.App.DB.Table("user_online").
-		Where("pf", pf).Where("gid", gid).
-		WhereIn("userId", UsersId).
-		Where("logTime", ">=", startTime).
-		Select("logTime", "userId").
-		Group("date,userId").
-		Scan(&UserLogin).Error
+	//err = global.App.DB.Table("user_online_copy1 as user_online").
+	//	Where("pf", pf).Where("gid", gid).
+	//	Raw("USE INDEX (DATE)").
+	//	WhereIn("userId", UsersId).
+	//	Where("logTime", ">=", startTime).
+	//	Select("logTime", "user_online.userId").
+	//	Group("date,user_online.userId").
+	//	Scan(&UserLogin).Error
+	//if err != nil {
+	//	return nil, err
+	//}
+
+	// 使用 Raw 方法执行带有 FORCE INDEX 的 SQL 查询
+	query := `
+		SELECT 
+			logTime,
+			user_online.userId 
+		FROM 
+			user_online AS user_online USE INDEX (date)
+			INNER JOIN (
+				SELECT userId 
+				FROM user 
+				WHERE pf = ? 
+				  AND gid = ? 
+				  AND createdAt >= ? 
+				  AND createdAt <= ?
+			) AS t ON t.userId = user_online.userId 
+		WHERE 
+			user_online.pf = ? 
+			AND user_online.gid = ? 
+			AND DATE(logTime) >= ?
+		GROUP BY 
+			date,
+			user_online.userId;
+	`
+
+	err = global.App.DB.Raw(query, pf, gid, startTime, endTime, pf, gid, startTime).Scan(&UserLogin).Error
 	if err != nil {
 		return nil, err
 	}
+
+	fmt.Printf("步骤3耗时: %v\n", time.Since(start))
+
 	//对这些数据进行整理
 	for _, v := range UserLogin {
 		//根据天进行分组,得出总共有多少
@@ -98,5 +143,162 @@ func RemainDataBydDay(types int, pf string, gid string, startTime string, endTim
 		res[day] = remain
 	}
 
+	fmt.Printf("步骤4耗时: %v\n", time.Since(start))
+
 	return res, nil
 }
+
+func RemainDataBydDayNew(types int, pf string, gid string, startTime string, endTime string) (map[string]map[string]interface{}, error) {
+	//目前只有types = 1
+
+	//对于传过来的endTime ,做一个处理
+	t, _ := time.Parse("2006-01-02", endTime)
+	endTimeData := t.AddDate(0, 0, 1).Format("2006-01-02")
+
+	UsersBydDay := utils.GetTimeDayDate(startTime, endTime) //用户分别是在哪天注册的
+	//先计算出这个时间内 ,每天的注册用户数量
+	var users []model.User
+	err := global.App.DB.Table("user").
+		Where("pf", pf).Where("gid", gid).
+		Where("createdAt", ">=", startTime).
+		Where("createdAt", "<=", endTimeData).
+		Select("userId", "createdAt").
+		Scan(&users).Error
+	if err != nil {
+		return nil, err
+	}
+	var UsersId []int
+	userIdMap := make(map[int]bool)
+
+	for _, user := range users {
+		userIdMap[user.UserId] = true
+		UsersBydDay[user.CreatedAt.Format("2006-01-02")] = append(UsersBydDay[user.CreatedAt.Format("2006-01-02")], user.UserId)
+	}
+	for user := range userIdMap {
+		UsersId = append(UsersId, user)
+	}
+
+	UserLoginBydDay := utils.GetTimeDayDate(startTime, endTime) //用户分别是在哪天注册的
+
+	for k, _ := range UserLoginBydDay {
+		//根据pf,gid,startTime,endTime 取出数据
+		UserLoginBydDay[k] = GetLocalActiveLog(gid, pf, k)
+	}
+
+	res := make(map[string]map[string]interface{})
+	for day, v := range UsersBydDay {
+		remain := make(map[string]interface{})
+		remain["count"] = len(v)
+		remain["date"] = day
+		//分别比较每一天的数据
+		for dayDate, loginDay := range UserLoginBydDay {
+			ok, remainDays := utils.CompareDates(dayDate, day)
+			if !ok {
+				continue
+			}
+			remain[remainDays] = math.Round(utils.IntersectionRate(v, loginDay)*100*100) / 100
+		}
+
+		res[day] = remain
+	}
+
+	return res, nil
+}
+
+func OnlineToFile() {
+
+	//分批读取数据
+	now := time.Now()
+	for i := 1; i <= 45; i++ {
+		var onlineData []model.UserOnline
+
+		date := now.AddDate(0, 0, -i).Format("20060102")
+
+		global.App.DB.Table("user_online_old").Where("date", date).Scan(&onlineData)
+
+		//根据gid和pf,分文件存储
+		for _, v := range onlineData {
+			SetLocalActiveLog(v.Gid, v.Pf, "", v.UserId, v.Type, v.LogTime)
+		}
+
+		fmt.Println(date, "数据归档完成,耗时:", time.Since(now))
+	}
+
+	//var onlineData []model.UserOnline
+	//
+	//date := "20250423"
+	//
+	//global.App.DB.Table("user_online").Where("date", date).Scan(&onlineData)
+	//
+	////根据gid和pf,分文件存储
+	//for _, v := range onlineData {
+	//	SetLocalActiveLog(v.Gid, v.Pf, "", v.UserId, v.Type, v.LogTime)
+	//}
+
+}
+
+func SetLocalActiveLog(gid string, pf string, openId string, userId int, types int, now time.Time) error {
+	// 生成日期目录路径(示例:./data/2024-06-15)
+	dateDir := now.Format("2006-01-02")
+	dirPath := filepath.Join("storage", dateDir)
+
+	// 创建日期目录(若不存在)
+	if err := os.MkdirAll(dirPath, 0755); err != nil {
+		return err
+	}
+
+	// 生成文件名(示例:g123_wechat.txt)
+	fileName := fmt.Sprintf("%s_%s.txt", gid, pf)
+	filePath := filepath.Join(dirPath, fileName)
+
+	// 追加写入文件
+	file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		return fmt.Errorf("failed to open file: %v", err)
+	}
+	defer file.Close()
+
+	// 写入数据(示例:1584,oXyZ123456\n)
+	if _, err := fmt.Fprintf(file, "%d,%d,%s\n", userId, types, now.Format("2006-01-02 15:04:05")); err != nil {
+		return fmt.Errorf("failed to write file: %v", err)
+	}
+
+	return nil
+}
+
+func GetLocalActiveLog(gid string, pf string, date string) []int {
+	var dir string
+	if config.Get("app.local") == "local" {
+		//url = "mongodb://localhost:27017"
+		dir = "storage"
+	} else {
+		dir = "/www/wwwroot/chunhao_receive/storage"
+	}
+
+	dirPath := filepath.Join(dir, date)
+	fileName := fmt.Sprintf("%s_%s.txt", gid, pf)
+	filePath := filepath.Join(dirPath, fileName)
+
+	file, _ := os.Open(filePath)
+
+	defer file.Close() // 确保函数结束时关闭文件
+
+	// 创建 Scanner 对象
+	scanner := bufio.NewScanner(file)
+
+	var res []int
+
+	// 逐行读取文件内容
+	lineNumber := 1
+	for scanner.Scan() {
+		line := scanner.Text() // 获取当前行内容
+		lineNumber++
+
+		userId, _ := strconv.Atoi(strings.Split(line, ",")[0])
+		if !utils.InArray(userId, res) {
+			res = append(res, userId)
+		}
+	}
+
+	return res
+}

+ 21 - 0
utils/array.go

@@ -63,3 +63,24 @@ func IntersectionRate(a, b []int) float64 {
 
 	return rate
 }
+
+// UnionOfSubarrays 计算二维数组中所有子数组的并集
+func UnionOfSubArrays(arr [][]int) []int {
+	// 使用 map 来存储唯一的元素
+	elementSet := make(map[int]struct{})
+
+	// 遍历二维数组
+	for _, subarray := range arr {
+		for _, num := range subarray {
+			elementSet[num] = struct{}{}
+		}
+	}
+
+	// 将 map 中的键提取为切片
+	result := make([]int, 0, len(elementSet))
+	for num := range elementSet {
+		result = append(result, num)
+	}
+
+	return result
+}

+ 1 - 1
utils/time.go

@@ -105,7 +105,7 @@ func CompareDates(dateA, dateB string) (bool, string) {
 
 	diff := int(tA.Sub(tB).Hours() / 24)
 
-	valuableDiff := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14, 30}
+	valuableDiff := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}
 
 	if InArray(diff, valuableDiff) {
 		return true, "+" + strconv.Itoa(diff) + "day"