diff --git a/migrate/migrate.go b/migrate/migrate.go index e97f60a..2213b7d 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -66,6 +66,7 @@ func Setup() { &models.Settings{}, &models.CronMessages{}, &models.HostedMessage{}, + &models.LoginLog{}, } for _, table := range tables { diff --git a/models/auth.go b/models/auth.go index 5d83a71..655ced8 100644 --- a/models/auth.go +++ b/models/auth.go @@ -26,6 +26,15 @@ func CheckAuth(username, password string) (bool, error) { return false, nil } +func GetUserByUsername(username string) (*Auth, error) { + var auth Auth + err := db.Where("username = ?", username).First(&auth).Error + if err != nil { + return nil, err + } + return &auth, nil +} + // EditUser 编辑用户信息 func EditUser(username string, data map[string]interface{}) error { if err := db.Model(&Auth{}).Where("username = ? ", username).Updates(data).Error; err != nil { diff --git a/models/login_log.go b/models/login_log.go new file mode 100644 index 0000000..6e7853a --- /dev/null +++ b/models/login_log.go @@ -0,0 +1,28 @@ +package models + +import "message-nest/pkg/util" + +type LoginLog struct { + ID uint `gorm:"autoIncrement;type:integer;primaryKey" json:"id"` + UserID int `json:"user_id" gorm:"type:int;index"` + Username string `json:"username" gorm:"type:varchar(100);default:'';index"` + IP string `json:"ip" gorm:"type:varchar(64);default:'';"` + UA string `json:"ua" gorm:"type:varchar(512);default:'';"` + CreatedAt util.Time `json:"created_on" gorm:"column:created_on;autoCreateTime"` +} + +func AddLoginLog(userID int, username string, ip string, ua string) error { + log := LoginLog{UserID: userID, Username: username, IP: ip, UA: ua} + return db.Create(&log).Error +} + +func GetRecentLoginLogs(limit int) ([]LoginLog, error) { + if limit <= 0 { + limit = 8 + } + var logs []LoginLog + err := db.Model(&LoginLog{}).Order("id DESC").Limit(limit).Find(&logs).Error + return logs, err +} + + diff --git a/routers/api/auth.go b/routers/api/auth.go index ca25808..ea13d7d 100644 --- a/routers/api/auth.go +++ b/routers/api/auth.go @@ -1,15 +1,16 @@ package api import ( - "fmt" - "net/http" + "fmt" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" - "message-nest/pkg/app" - "message-nest/pkg/e" - "message-nest/pkg/util" - "message-nest/service/auth_service" + "message-nest/pkg/app" + "message-nest/pkg/e" + "message-nest/pkg/util" + "message-nest/service/auth_service" + "message-nest/models" ) type auth struct { @@ -51,7 +52,12 @@ func GetAuth(c *gin.Context) { return } - appG.CResponse(http.StatusOK, "登录成功!", map[string]string{ + // 查询用户ID并记录登录日志 + if u, _ := models.GetUserByUsername(req.Username); u != nil { + _ = models.AddLoginLog(u.ID, req.Username, c.ClientIP(), c.GetHeader("User-Agent")) + } + + appG.CResponse(http.StatusOK, "登录成功!", map[string]string{ "token": token, }) } diff --git a/routers/api/v1/login_log.go b/routers/api/v1/login_log.go new file mode 100644 index 0000000..0660053 --- /dev/null +++ b/routers/api/v1/login_log.go @@ -0,0 +1,23 @@ +package v1 + +import ( + "github.com/gin-gonic/gin" + "message-nest/pkg/app" + "message-nest/pkg/e" + "message-nest/models" +) + +// GetRecentLoginLogs 最近登录日志(默认8条) +func GetRecentLoginLogs(c *gin.Context) { + appG := app.Gin{C: c} + logs, err := models.GetRecentLoginLogs(8) + if err != nil { + appG.CResponse(e.ERROR, err.Error(), nil) + return + } + appG.CResponse(e.SUCCESS, "success", gin.H{ + "lists": logs, + }) +} + + diff --git a/routers/router.go b/routers/router.go index 355f033..12e7b69 100644 --- a/routers/router.go +++ b/routers/router.go @@ -82,6 +82,9 @@ func InitRouter(f embed.FS) *gin.Engine { apiV1.POST("/settings/reset", v1.RestDefaultSettings) apiV1.GET("/settings/getsetting", v1.GetUserSetting) + // login logs + apiV1.GET("/loginlogs/recent", v1.GetRecentLoginLogs) + // statistic apiV1.GET("/statistic", v1.GetStatisticData) diff --git a/web/src/components/pages/settings/LoginLogs.vue b/web/src/components/pages/settings/LoginLogs.vue new file mode 100644 index 0000000..87f5dcf --- /dev/null +++ b/web/src/components/pages/settings/LoginLogs.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/web/src/components/pages/settings/Settings.vue b/web/src/components/pages/settings/Settings.vue index 734370c..00690d2 100644 --- a/web/src/components/pages/settings/Settings.vue +++ b/web/src/components/pages/settings/Settings.vue @@ -5,6 +5,7 @@ import PasswordSettings from './PasswordSettings.vue' import LogsSettings from './LogsSettings.vue' import SiteSettings from './SiteSettings.vue' import AboutSettings from './AboutSettings.vue' +import LoginLogs from './LoginLogs.vue' // 当前选中的设置项 const activeTab = ref('password') @@ -30,6 +31,9 @@ const activeTab = ref('password') + + + diff --git a/web/src/components/pages/settings/SettingsSidebar.vue b/web/src/components/pages/settings/SettingsSidebar.vue index 166fbc3..9988dc0 100644 --- a/web/src/components/pages/settings/SettingsSidebar.vue +++ b/web/src/components/pages/settings/SettingsSidebar.vue @@ -19,6 +19,7 @@ defineEmits() const settingsMenu = [ { id: 'password', name: '重置密码', icon: KeyIcon }, { id: 'logs', name: '日志清理', icon: TrashIcon }, + { id: 'loginlogs', name: '登录日志', icon: InfoIcon }, { id: 'site', name: '站点设置', icon: SettingsIcon }, { id: 'about', name: '站点关于', icon: InfoIcon } ]