mirror of https://gitee.com/godoos/godoos.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
5.9 KiB
249 lines
5.9 KiB
package middleware
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"godocms/common"
|
|
"godocms/libs"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// UserActivity 结构体用于存储用户的单个活动数据
|
|
type UserActivity struct {
|
|
Duration int64 `json:"duration"` // 修改为 int64 类型
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// UserActivities 结构体用于存储用户的多个活动数据
|
|
type UserActivities map[string]UserActivity
|
|
|
|
// UserStatus 结构体用于存储每个 IP 地址的状态和活动数据
|
|
type UserStatus struct {
|
|
Activities UserActivities `json:"activities"`
|
|
LastSeen time.Time `json:"last_seen"`
|
|
}
|
|
|
|
// UserStatuses 映射表用于存储每个 IP 地址的状态和活动数据
|
|
var (
|
|
UserStatusesMutex sync.Mutex
|
|
UserStatuses sync.Map
|
|
)
|
|
var ifWrite = false
|
|
|
|
// func init() {
|
|
// InitOnlineFile()
|
|
// }
|
|
|
|
// recordUserActivity 中间件记录用户的 IP 地址、请求的 URL 和开始时间
|
|
func RecordUserActivity() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
url := c.Request.URL.Path // 获取请求的 URL
|
|
if strings.HasPrefix(url, "/static/") || strings.HasPrefix(url, "/upload/") {
|
|
c.Next() // 继续后续处理
|
|
return
|
|
}
|
|
ip := libs.GetIpAddress(c) // 获取客户端 IP 地址
|
|
currentTime := time.Now()
|
|
//log.Printf("IP:%s,URL:%s,Time:%s", ip, url, currentTime)
|
|
// 检查 URL 是否以 /static/ 开头,如果是则跳过记录
|
|
|
|
// 更新用户的活动数据
|
|
UserStatusesMutex.Lock()
|
|
userStatus, ok := UserStatuses.Load(ip)
|
|
if ok {
|
|
// 如果用户已经存在,则更新其停留时长
|
|
status := userStatus.(UserStatus)
|
|
if activity, exists := status.Activities[url]; exists {
|
|
durationInSeconds := int64(currentTime.Sub(activity.Timestamp).Seconds()) // 转换为秒
|
|
activity.Duration += durationInSeconds
|
|
activity.Timestamp = currentTime
|
|
status.Activities[url] = activity
|
|
} else {
|
|
status.Activities[url] = UserActivity{
|
|
Timestamp: currentTime,
|
|
Duration: 0,
|
|
}
|
|
}
|
|
status.LastSeen = currentTime
|
|
userStatus = status
|
|
} else {
|
|
// 如果用户不存在,则创建新的状态记录
|
|
userStatus = UserStatus{
|
|
Activities: UserActivities{
|
|
url: UserActivity{
|
|
Timestamp: currentTime,
|
|
Duration: 0,
|
|
},
|
|
},
|
|
LastSeen: currentTime,
|
|
}
|
|
}
|
|
|
|
// 将用户状态数据保存回映射表
|
|
UserStatuses.Store(ip, userStatus)
|
|
ifWrite = true
|
|
UserStatusesMutex.Unlock()
|
|
c.Next() // 继续后续处理
|
|
}
|
|
}
|
|
|
|
// 读取 JSON 文件
|
|
func InitOnlineFile() error {
|
|
logPath := getLogPath()
|
|
//log.Printf("logpath is:%s", logPath)
|
|
data, err := os.ReadFile(logPath)
|
|
if os.IsNotExist(err) {
|
|
return nil // 文件不存在,不需要做任何事情
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 创建临时映射表用于反序列化
|
|
var mapped map[string]UserStatus
|
|
if err := json.Unmarshal(data, &mapped); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 清空 UserStatuses 映射表
|
|
UserStatusesMutex.Lock()
|
|
UserStatuses = sync.Map{}
|
|
UserStatusesMutex.Unlock()
|
|
|
|
// 将数据加载到 UserStatuses 映射表中
|
|
for k, v := range mapped {
|
|
UserStatuses.Store(k, v)
|
|
}
|
|
StartTicker()
|
|
return nil
|
|
}
|
|
|
|
// 启动定时器
|
|
func StartTicker() {
|
|
ticker := time.NewTicker(1 * time.Minute) // 每分钟检查一次
|
|
go func() {
|
|
for range ticker.C {
|
|
if ifWrite {
|
|
if err := WriteJSONFile(); err != nil {
|
|
fmt.Println("Error writing to file:", err)
|
|
}
|
|
ifWrite = false
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// 获取日志文件目录
|
|
func getLogPath() string {
|
|
filename := fmt.Sprintf("online_%s.json", time.Now().Format("2006-01-02"))
|
|
// 获取程序执行文件所在目录的绝对路径
|
|
exePath, err := os.Executable()
|
|
if err != nil {
|
|
return filename
|
|
}
|
|
exeDir := filepath.Dir(exePath)
|
|
|
|
// 构建 logs 文件夹的绝对路径
|
|
logDir := filepath.Join(exeDir, "logs")
|
|
if !libs.PathExists(logDir) {
|
|
os.Mkdir(logDir, 0755)
|
|
}
|
|
|
|
return filepath.Join(logDir, filename)
|
|
}
|
|
|
|
// 写入 JSON 文件
|
|
func WriteJSONFile() error {
|
|
// 将 sync.Map 转换为普通的映射表
|
|
mapped := make(map[string]UserStatus)
|
|
UserStatuses.Range(func(key, value interface{}) bool {
|
|
mapped[key.(string)] = value.(UserStatus)
|
|
return true
|
|
})
|
|
|
|
data, err := json.MarshalIndent(mapped, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 将文件写入 logs 目录
|
|
logPath := getLogPath()
|
|
return os.WriteFile(logPath, data, 0644)
|
|
}
|
|
|
|
func UserAccessRecord() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
uid := c.GetInt64("userId")
|
|
if uid == 0 {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
UserStatusesMutex.Lock()
|
|
// 获取缓存的数据
|
|
var origin map[int64]int64
|
|
var ok bool
|
|
data, _ := common.Cache.Get("user_online")
|
|
if data == nil {
|
|
origin = make(map[int64]int64)
|
|
} else {
|
|
if origin, ok = data.(map[int64]int64); !ok {
|
|
origin = make(map[int64]int64)
|
|
}
|
|
}
|
|
|
|
origin[uid] = time.Now().Unix()
|
|
common.Cache.Set("user_online", origin, 60*24)
|
|
UserStatusesMutex.Unlock()
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// 启动定时心跳检查任务
|
|
func StartHeartbeatCheckOnline() {
|
|
ticker := time.NewTicker(3 * time.Minute) // 每 3 分钟执行一次
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
// 每次心跳检查,检查不活跃的用户
|
|
checkInactiveUsers()
|
|
}
|
|
}
|
|
|
|
// 检查不在线的用户
|
|
func checkInactiveUsers() {
|
|
UserStatusesMutex.Lock() // 锁定对缓存的访问
|
|
defer UserStatusesMutex.Unlock()
|
|
|
|
now := time.Now().Unix()
|
|
// 获取缓存的数据
|
|
var origin map[int64]int64
|
|
var ok bool
|
|
data, _ := common.Cache.Get("user_online")
|
|
if data == nil {
|
|
log.Println("No user activity data found in cache.")
|
|
return
|
|
}
|
|
|
|
// 安全地进行类型断言
|
|
if origin, ok = data.(map[int64]int64); !ok {
|
|
log.Println("Cache data is not of the expected type.")
|
|
return
|
|
}
|
|
|
|
// 遍历所有用户的活动记录,检查是否有不在线的用户
|
|
for uid, lastActivityTime := range origin {
|
|
if now-lastActivityTime > 30*60 { // 如果用户的最后活动时间超过 30 分钟, 视为不在线
|
|
delete(origin, uid)
|
|
}
|
|
}
|
|
|
|
common.Cache.Set("user_online", origin, 60*24)
|
|
}
|
|
|