操作日志和访问日志

This commit is contained in:
2025-11-11 22:32:46 +08:00
parent efc079c0e5
commit 12a0ff8afc
17 changed files with 2300 additions and 4 deletions
+56
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"server/models"
"server/services"
"strconv"
"strings"
"time"
@@ -188,6 +189,24 @@ func (c *AuthController) Login() {
_, _ = o.Raw("UPDATE yz_tenant_employees SET last_login_time = ?, last_login_ip = ? WHERE id = ?", loginTime, clientIP, employee.Id).Exec()
}
// 记录登录操作
loginLog := &models.OperationLog{
TenantId: tenantId,
UserId: userId,
Username: usernameForToken,
Module: "auth",
ResourceType: "user",
Operation: "LOGIN",
IpAddress: clientIP,
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: "POST",
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: loginTime,
}
_ = services.AddOperationLog(loginLog)
c.Data["json"] = map[string]interface{}{
"code": 0,
"message": "登录成功",
@@ -205,6 +224,43 @@ func (c *AuthController) Login() {
// Logout 处理登出请求
func (c *AuthController) Logout() {
// 获取登出的用户信息
userIdStr := c.GetString("user_id")
username := c.GetString("username")
tenantIdStr := c.GetString("tenant_id")
var userId, tenantId int
if userIdStr != "" {
id, err := strconv.Atoi(userIdStr)
if err == nil {
userId = id
}
}
if tenantIdStr != "" {
id, err := strconv.Atoi(tenantIdStr)
if err == nil {
tenantId = id
}
}
// 记录登出操作
logoutLog := &models.OperationLog{
TenantId: tenantId,
UserId: userId,
Username: username,
Module: "auth",
ResourceType: "user",
Operation: "LOGOUT",
IpAddress: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: "POST",
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: time.Now(),
}
_ = services.AddOperationLog(logoutLog)
// 在实际应用中,这里需要处理JWT或Session的清除
c.Data["json"] = map[string]interface{}{
"success": true,
+219 -1
View File
@@ -5,6 +5,7 @@ import (
"server/models"
"server/services"
"strconv"
"time"
"github.com/beego/beego/v2/server/web"
)
@@ -160,6 +161,32 @@ func (c *DictController) AddDictType() {
"data": map[string]interface{}{"id": id},
}
c.ServeJSON()
// 记录操作日志 - 创建字典类型
go func() {
// 异步写日志,避免阻塞请求响应
rid := int(id)
newVal, _ := json.Marshal(dictType)
log := &models.OperationLog{
TenantId: dictType.TenantId,
UserId: userId,
Username: username,
Module: "dict",
ResourceType: "dict_type",
ResourceId: &rid,
Operation: "CREATE",
Description: "创建字典类型",
NewValue: string(newVal),
IpAddress: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: c.Ctx.Input.Method(),
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: time.Now(),
}
_ = services.AddOperationLog(log)
}()
}
// UpdateDictType 更新字典类型
@@ -194,6 +221,15 @@ func (c *DictController) UpdateDictType() {
}
}
// 获取用户ID
userIdData := c.Ctx.Input.GetData("userId")
userId := 0
if userIdData != nil {
if uid, ok := userIdData.(int); ok {
userId = uid
}
}
dictType.Id = id
dictType.UpdateBy = username
@@ -212,6 +248,31 @@ func (c *DictController) UpdateDictType() {
"message": "更新成功",
}
c.ServeJSON()
// 记录操作日志 - 更新字典类型
go func() {
newVal, _ := json.Marshal(dictType)
// 旧值可以从服务层获取,如果需要更详细的旧值,可以在 UpdateDictType 前查询并传入
log := &models.OperationLog{
TenantId: dictType.TenantId,
UserId: userId,
Username: username,
Module: "dict",
ResourceType: "dict_type",
ResourceId: &dictType.Id,
Operation: "UPDATE",
Description: "更新字典类型",
NewValue: string(newVal),
IpAddress: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: c.Ctx.Input.Method(),
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: time.Now(),
}
_ = services.AddOperationLog(log)
}()
}
// DeleteDictType 删除字典类型
@@ -227,7 +288,7 @@ func (c *DictController) DeleteDictType() {
return
}
// 获取租户ID
// 获取租户ID、用户名和用户ID 用于记录日志
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
@@ -235,6 +296,20 @@ func (c *DictController) DeleteDictType() {
tenantId = tid
}
}
usernameData := c.Ctx.Input.GetData("username")
username := ""
if usernameData != nil {
if u, ok := usernameData.(string); ok {
username = u
}
}
userIdData := c.Ctx.Input.GetData("userId")
userId := 0
if userIdData != nil {
if uid, ok := userIdData.(int); ok {
userId = uid
}
}
err = services.DeleteDictType(id, tenantId)
if err != nil {
@@ -251,6 +326,29 @@ func (c *DictController) DeleteDictType() {
"message": "删除成功",
}
c.ServeJSON()
// 记录操作日志 - 删除字典类型
go func() {
rid := id
log := &models.OperationLog{
TenantId: tenantId,
UserId: userId,
Username: username,
Module: "dict",
ResourceType: "dict_type",
ResourceId: &rid,
Operation: "DELETE",
Description: "删除字典类型",
IpAddress: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: c.Ctx.Input.Method(),
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: time.Now(),
}
_ = services.AddOperationLog(log)
}()
}
// GetDictItems 获取字典项列表
@@ -380,6 +478,46 @@ func (c *DictController) AddDictItem() {
"data": map[string]interface{}{"id": id},
}
c.ServeJSON()
// 记录操作日志 - 创建字典项
go func() {
// 获取用户ID和租户ID
userIdData := c.Ctx.Input.GetData("userId")
userId := 0
if userIdData != nil {
if uid, ok := userIdData.(int); ok {
userId = uid
}
}
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
rid := int(id)
newVal, _ := json.Marshal(dictItem)
log := &models.OperationLog{
TenantId: tenantId,
UserId: userId,
Username: username,
Module: "dict",
ResourceType: "dict_item",
ResourceId: &rid,
Operation: "CREATE",
Description: "创建字典项",
NewValue: string(newVal),
IpAddress: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: c.Ctx.Input.Method(),
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: time.Now(),
}
_ = services.AddOperationLog(log)
}()
}
// UpdateDictItem 更新字典项
@@ -432,6 +570,41 @@ func (c *DictController) UpdateDictItem() {
"message": "更新成功",
}
c.ServeJSON()
// 记录操作日志 - 更新字典项
go func() {
// 获取用户ID和租户ID
userIdData := c.Ctx.Input.GetData("userId")
userId := 0
if userIdData != nil {
if uid, ok := userIdData.(int); ok {
userId = uid
}
}
tenantId := 0
// 通过字典项id尝试获取所属字典类型并推断租户(可选)
// 这里先不查询,留 0 或者可从前端传入
newVal, _ := json.Marshal(dictItem)
log := &models.OperationLog{
TenantId: tenantId,
UserId: userId,
Username: username,
Module: "dict",
ResourceType: "dict_item",
ResourceId: &dictItem.Id,
Operation: "UPDATE",
Description: "更新字典项",
NewValue: string(newVal),
IpAddress: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: c.Ctx.Input.Method(),
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: time.Now(),
}
_ = services.AddOperationLog(log)
}()
}
// DeleteDictItem 删除字典项
@@ -462,6 +635,51 @@ func (c *DictController) DeleteDictItem() {
"message": "删除成功",
}
c.ServeJSON()
// 记录操作日志 - 删除字典项
go func() {
// 获取用户ID和租户ID
userIdData := c.Ctx.Input.GetData("userId")
userId := 0
if userIdData != nil {
if uid, ok := userIdData.(int); ok {
userId = uid
}
}
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
usernameData := c.Ctx.Input.GetData("username")
username := ""
if usernameData != nil {
if u, ok := usernameData.(string); ok {
username = u
}
}
rid := id
log := &models.OperationLog{
TenantId: tenantId,
UserId: userId,
Username: username,
Module: "dict",
ResourceType: "dict_item",
ResourceId: &rid,
Operation: "DELETE",
Description: "删除字典项",
IpAddress: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Input.Header("User-Agent"),
RequestMethod: c.Ctx.Input.Method(),
RequestUrl: c.Ctx.Input.URL(),
Status: 1,
Duration: 0,
CreateTime: time.Now(),
}
_ = services.AddOperationLog(log)
}()
}
// GetDictItemsByCode 根据字典编码获取字典项(用于业务查询)
+247
View File
@@ -0,0 +1,247 @@
package controllers
import (
"server/models"
"server/services"
"strconv"
"time"
"github.com/beego/beego/v2/server/web"
)
// OperationLogController 操作日志控制器
type OperationLogController struct {
web.Controller
}
// GetOperationLogs 获取操作日志列表
func (c *OperationLogController) GetOperationLogs() {
// 获取租户ID和用户ID
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
userIdData := c.Ctx.Input.GetData("userId")
userId := 0
if userIdData != nil {
if uid, ok := userIdData.(int); ok {
userId = uid
}
}
// 获取查询参数
pageNum, _ := c.GetInt("page_num", 1)
pageSize, _ := c.GetInt("page_size", 20)
module := c.GetString("module")
operation := c.GetString("operation")
filterUserId, _ := c.GetInt("user_id", 0)
startTimeStr := c.GetString("start_time")
endTimeStr := c.GetString("end_time")
var startTime, endTime *time.Time
if startTimeStr != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTimeStr); err == nil {
startTime = &t
}
}
if endTimeStr != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTimeStr); err == nil {
endTime = &t
}
}
// 如果指定了用户ID筛选,使用该ID,否则使用当前用户ID
queryUserId := filterUserId
if queryUserId <= 0 {
queryUserId = userId
}
logs, total, err := services.GetOperationLogs(tenantId, queryUserId, module, operation, startTime, endTime, pageNum, pageSize)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "查询日志失败: " + err.Error(),
}
c.ServeJSON()
return
}
// 为每条日志批量解析模块名称(减少对菜单表的重复查询)
type LogWithModuleName struct {
*models.OperationLog
ModuleName string `json:"module_name"`
}
// 收集唯一 module 列表
moduleSet := make(map[string]struct{})
modules := make([]string, 0)
for _, l := range logs {
m := l.Module
if m == "" {
continue
}
if _, ok := moduleSet[m]; !ok {
moduleSet[m] = struct{}{}
modules = append(modules, m)
}
}
moduleNamesMap := map[string]string{}
if len(modules) > 0 {
if mm, err := services.GetModuleNames(modules); err == nil {
moduleNamesMap = mm
}
}
logsWithName := make([]*LogWithModuleName, len(logs))
for i, log := range logs {
name := ""
if v, ok := moduleNamesMap[log.Module]; ok {
name = v
} else {
// fallback 单个解析(保留老逻辑)
name = services.GetModuleName(log.Module)
}
logsWithName[i] = &LogWithModuleName{
OperationLog: log,
ModuleName: name,
}
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": logsWithName,
"total": total,
"page": pageNum,
"page_size": pageSize,
}
c.ServeJSON()
}
// GetOperationLogById 根据ID获取操作日志
func (c *OperationLogController) GetOperationLogById() {
idStr := c.Ctx.Input.Param(":id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil || id <= 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "参数错误",
}
c.ServeJSON()
return
}
log, err := services.GetOperationLogById(id)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "日志不存在",
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": log,
}
c.ServeJSON()
}
// GetUserStats 获取用户操作统计
func (c *OperationLogController) GetUserStats() {
userIdData := c.Ctx.Input.GetData("userId")
userId := 0
if userIdData != nil {
if uid, ok := userIdData.(int); ok {
userId = uid
}
}
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
days, _ := c.GetInt("days", 7)
stats, err := services.GetUserOperationStats(tenantId, userId, days)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "查询统计失败: " + err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": stats,
}
c.ServeJSON()
}
// GetTenantStats 获取租户操作统计
func (c *OperationLogController) GetTenantStats() {
tenantIdData := c.Ctx.Input.GetData("tenantId")
tenantId := 0
if tenantIdData != nil {
if tid, ok := tenantIdData.(int); ok {
tenantId = tid
}
}
days, _ := c.GetInt("days", 7)
stats, err := services.GetTenantOperationStats(tenantId, days)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "查询统计失败: " + err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"data": stats,
}
c.ServeJSON()
}
// ClearOldLogs 清空旧日志
func (c *OperationLogController) ClearOldLogs() {
keepDays, _ := c.GetInt("keep_days", 90)
rowsAffected, err := services.DeleteOldLogs(keepDays)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "删除日志失败: " + err.Error(),
}
c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "删除成功",
"data": map[string]interface{}{
"deleted_count": rowsAffected,
"keep_days": keepDays,
},
}
c.ServeJSON()
}