feat: add login log record
This commit is contained in:
@@ -66,6 +66,7 @@ func Setup() {
|
||||
&models.Settings{},
|
||||
&models.CronMessages{},
|
||||
&models.HostedMessage{},
|
||||
&models.LoginLog{},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user