feat: add login log record

This commit is contained in:
engigu
2025-10-01 00:19:48 +08:00
parent 456d3aaf46
commit 6dbc8352e9
9 changed files with 156 additions and 8 deletions
+1
View File
@@ -66,6 +66,7 @@ func Setup() {
&models.Settings{},
&models.CronMessages{},
&models.HostedMessage{},
&models.LoginLog{},
}
for _, table := range tables {
+9
View File
@@ -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 {
+28
View File
@@ -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
}
+14 -8
View File
@@ -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,
})
}
+23
View File
@@ -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,
})
}
+3
View File
@@ -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)
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
// @ts-ignore
import { request } from '@/api/api'
interface LoginLog {
id: number
user_id: number
username: string
ip: string
ua: string
created_on: string
}
const loading = ref(false)
const logs = ref<LoginLog[]>([])
const fetchLogs = async () => {
loading.value = true
try {
const rsp = await request.get('/loginlogs/recent')
const data = rsp.data
if (data && data.code === 200 && data.data) {
logs.value = data.data.lists || []
}
} finally {
loading.value = false
}
}
onMounted(fetchLogs)
</script>
<template>
<div class="space-y-3">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">最近登录日志</h2>
<button class="text-sm text-muted-foreground hover:text-foreground" @click="fetchLogs" :disabled="loading">
{{ loading ? '刷新中...' : '刷新' }}
</button>
</div>
<div class="overflow-hidden rounded-md border border-border">
<table class="w-full text-sm">
<thead>
<tr class="bg-muted">
<th class="px-3 py-2 text-left">用户名</th>
<th class="px-3 py-2 text-left">IP</th>
<th class="px-3 py-2 text-left">UA</th>
<th class="px-3 py-2 text-left">登录时间</th>
</tr>
</thead>
<tbody>
<tr v-if="!logs.length">
<td colspan="4" class="px-3 py-6">
<div class="flex items-center justify-center">
<span class="text-sm text-muted-foreground">暂无数据</span>
</div>
</td>
</tr>
<tr v-for="item in logs" :key="item.id" class="border-t border-border">
<td class="px-3 py-2">{{ item.username }}</td>
<td class="px-3 py-2">{{ item.ip }}</td>
<td class="px-3 py-2 truncate max-w-[420px]" :title="item.ua">{{ item.ua }}</td>
<td class="px-3 py-2">{{ item.created_on }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
@@ -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')
<!-- 站点设置 -->
<SiteSettings v-if="activeTab === 'site'" />
<!-- 登录日志 -->
<LoginLogs v-if="activeTab === 'loginlogs'" />
<!-- 站点关于 -->
<AboutSettings v-if="activeTab === 'about'" />
</div>
@@ -19,6 +19,7 @@ defineEmits<Emits>()
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 }
]