增加登录日志功能

This commit is contained in:
2026-08-26 15:01:35 +08:00
parent a4f7a97763
commit e60cd00395
9 changed files with 1008 additions and 1 deletions
+12 -1
View File
@@ -35,6 +35,13 @@ func (c *BackendAuthController) serveJSON(data map[string]interface{}) {
_ = c.ServeJSON()
}
// recordLoginLog 记录登录日志(成功/失败均记录,失败不影响登录流程)
func (c *BackendAuthController) recordLoginLog(tid *uint64, userID uint64, account, userName, tenantName, loginType string, status int8, message string) {
ip := c.Ctx.Input.IP()
userAgent := c.Ctx.Request.UserAgent()
RecordLoginLog(tid, userID, account, userName, tenantName, loginType, status, message, ip, userAgent)
}
// LoginBackend backend 登录(需要租户)
func (c *BackendAuthController) LoginBackend() {
var req backendAuthLoginRequest
@@ -93,10 +100,14 @@ func (c *BackendAuthController) LoginBackend() {
token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password)
if err != nil {
c.recordLoginLog(nil, 0, req.Account, "", req.TenantName, "password", 0, err.Error())
c.serveJSON(map[string]interface{}{"code": 401, "msg": err.Error()})
return
}
tid := loginUser.Tid
c.recordLoginLog(&tid, loginUser.ID, loginUser.Account, loginUser.Name, req.TenantName, "password", 1, "登录成功")
c.serveJSON(map[string]interface{}{
"code": 200,
"msg": "登录成功",
@@ -319,4 +330,4 @@ func (c *BackendAuthController) SendResetCode() {
"code": 501,
"msg": "发送找回密码验证码暂未实现",
})
}
}
+284
View File
@@ -0,0 +1,284 @@
package controllers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// BackendLoginLogController 登录日志(yz_system_login_log)
type BackendLoginLogController struct {
beego.Controller
}
func (c *BackendLoginLogController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *BackendLoginLogController) backendClaims() (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, fmt.Errorf("未登录")
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, fmt.Errorf("认证信息格式错误")
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, fmt.Errorf("无效的token")
}
if claims.UserType != "backend" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
// RecordLoginLog 写入一条登录日志(登录成功/失败均记录,失败不影响登录流程)。
func RecordLoginLog(tid *uint64, userID uint64, account, userName, tenantName, loginType string, status int8, message string, ip, userAgent string) {
if len(userAgent) > 500 {
userAgent = userAgent[:500]
}
if len(message) > 255 {
message = message[:255]
}
row := &models.SystemLoginLog{
Tid: tid,
UserID: userID,
Account: account,
UserName: userName,
TenantName: tenantName,
LoginType: loginType,
Status: status,
Message: message,
IP: ip,
UserAgent: userAgent,
}
if _, err := models.Orm.Insert(row); err != nil {
fmt.Println("record login log error:", err)
}
}
// List GET /backend/loginLogs?page=1&pageSize=20&keyword=&status=&loginType=&startTime=&endTime=
func (c *BackendLoginLogController) List() {
if _, err := c.backendClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 200 {
pageSize = 200
}
keyword := strings.TrimSpace(c.GetString("keyword"))
statusStr := strings.TrimSpace(c.GetString("status"))
loginType := strings.TrimSpace(c.GetString("loginType"))
startTimeStr := strings.TrimSpace(c.GetString("startTime"))
endTimeStr := strings.TrimSpace(c.GetString("endTime"))
qs := models.Orm.QueryTable(new(models.SystemLoginLog)).Filter("delete_time__isnull", true)
cond := orm.NewCondition()
needCond := false
if statusStr != "" {
if st, err := strconv.Atoi(statusStr); err == nil {
cond = cond.And("status", st)
needCond = true
}
}
if loginType != "" {
cond = cond.And("login_type", loginType)
needCond = true
}
if keyword != "" {
kw := orm.NewCondition().
Or("account__icontains", keyword).
Or("user_name__icontains", keyword).
Or("tenant_name__icontains", keyword).
Or("ip__icontains", keyword)
if uid, err := strconv.ParseUint(keyword, 10, 64); err == nil && uid > 0 {
kw = kw.Or("user_id", uid)
}
cond = cond.AndCond(kw)
needCond = true
}
if t, err := parseTimeFlexible(startTimeStr); err == nil && !t.IsZero() {
cond = cond.And("create_time__gte", t)
needCond = true
}
if t, err := parseTimeFlexible(endTimeStr); err == nil && !t.IsZero() {
cond = cond.And("create_time__lte", t)
needCond = true
}
if needCond {
qs = qs.SetCond(cond)
}
total, err := qs.Count()
if err != nil {
c.jsonErr(500, 500, "获取登录日志失败: "+err.Error())
return
}
var rows []models.SystemLoginLog
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
if err != nil {
c.jsonErr(500, 500, "获取登录日志失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
item := map[string]interface{}{
"id": rows[i].ID,
"tid": rows[i].Tid,
"user_id": rows[i].UserID,
"account": rows[i].Account,
"user_name": rows[i].UserName,
"tenant_name": rows[i].TenantName,
"login_type": rows[i].LoginType,
"status": rows[i].Status,
"message": rows[i].Message,
"ip": rows[i].IP,
"user_agent": rows[i].UserAgent,
"create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"),
}
list = append(list, item)
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"list": list,
"total": total,
},
}
_ = c.ServeJSON()
}
// Detail GET /backend/loginLogs/:id
func (c *BackendLoginLogController) Detail() {
if _, err := c.backendClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
var row models.SystemLoginLog
err = models.Orm.QueryTable(new(models.SystemLoginLog)).
Filter("id", id).
Filter("delete_time__isnull", true).
One(&row)
if err != nil {
c.jsonErr(404, 404, "记录不存在")
return
}
out := map[string]interface{}{
"id": row.ID,
"tid": row.Tid,
"user_id": row.UserID,
"account": row.Account,
"user_name": row.UserName,
"tenant_name": row.TenantName,
"login_type": row.LoginType,
"status": row.Status,
"message": row.Message,
"ip": row.IP,
"user_agent": row.UserAgent,
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out}
_ = c.ServeJSON()
}
// Delete DELETE /backend/loginLogs/:id
func (c *BackendLoginLogController) Delete() {
if _, err := c.backendClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
now := time.Now()
n, err := models.Orm.QueryTable(new(models.SystemLoginLog)).
Filter("id", id).
Filter("delete_time__isnull", true).
Update(map[string]interface{}{"delete_time": now})
if err != nil {
c.jsonErr(500, 500, "删除失败: "+err.Error())
return
}
if n == 0 {
c.jsonErr(404, 404, "记录不存在")
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
_ = c.ServeJSON()
}
type backendLoginLogBatchDeletePayload struct {
IDs []uint64 `json:"ids"`
}
// BatchDelete POST /backend/loginLogs/batchDelete
func (c *BackendLoginLogController) BatchDelete() {
if _, err := c.backendClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
var p backendLoginLogBatchDeletePayload
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if len(p.IDs) == 0 {
c.jsonErr(400, 400, "请选择要删除的日志")
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.SystemLoginLog)).
Filter("id__in", p.IDs).
Filter("delete_time__isnull", true).
Update(map[string]interface{}{"delete_time": now})
if err != nil {
c.jsonErr(500, 500, "批量删除失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"}
_ = c.ServeJSON()
}
+1
View File
@@ -50,6 +50,7 @@ func Init(_ string) {
new(SystemFilesCategory),
new(SystemSMSTask),
new(SystemOperationLog),
new(SystemLoginLog),
new(SystemDomainPool),
new(SystemTenantDomain),
new(SystemModules),
+24
View File
@@ -0,0 +1,24 @@
package models
import "time"
// SystemLoginLog 登录日志表 yz_system_login_log
type SystemLoginLog struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid *uint64 `orm:"column(tid);null" json:"tid"`
UserID uint64 `orm:"column(user_id);default(0)" json:"user_id"`
Account string `orm:"column(account);size(64);default('')" json:"account"`
UserName string `orm:"column(user_name);size(64);default('')" json:"user_name"`
TenantName string `orm:"column(tenant_name);size(64);default('')" json:"tenant_name"`
LoginType string `orm:"column(login_type);size(20);default('password')" json:"login_type"`
Status int8 `orm:"column(status);default(1)" json:"status"`
Message string `orm:"column(message);size(255);default('')" json:"message"`
IP string `orm:"column(ip);size(50);default('')" json:"ip"`
UserAgent string `orm:"column(user_agent);size(500);default('')" json:"user_agent"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *SystemLoginLog) TableName() string {
return "yz_system_login_log"
}
+5
View File
@@ -41,6 +41,11 @@ func RegisterAuthRoutes() {
beego.Router("/backend/operationLogs/:id", &controllers.BackendOperationLogController{}, "get:Detail;delete:Delete")
beego.Router("/backend/operationLogs/batchDelete", &controllers.BackendOperationLogController{}, "post:BatchDelete")
// 登录日志(yz_system_login_log)
beego.Router("/backend/loginLogs", &controllers.BackendLoginLogController{}, "get:List")
beego.Router("/backend/loginLogs/:id", &controllers.BackendLoginLogController{}, "get:Detail;delete:Delete")
beego.Router("/backend/loginLogs/batchDelete", &controllers.BackendLoginLogController{}, "post:BatchDelete")
// 租户站点设置
beego.Router("/backend/normalInfos", &controllers.BackendSiteSettingsController{}, "get:GetNormalInfos")
beego.Router("/backend/saveNormalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveNormalInfos")