feat: 添加日志管理和敏感词过滤功能

- 新增日志管理模块:操作日志、登录日志、数据变更日志
- 新增敏感词过滤器 (sanitizer)
- 新增日志异步写入管理器
- 新增日志定时清理服务
- 优化帖子相关服务和上传服务
This commit is contained in:
2026-03-15 02:25:10 +08:00
parent d3065b30cc
commit ebd9cb8b04
31 changed files with 2753 additions and 60 deletions

View File

@@ -2,6 +2,8 @@ package handler
import (
"context"
"crypto/sha256"
"fmt"
"strconv"
"github.com/gin-gonic/gin"
@@ -17,6 +19,7 @@ type UserHandler struct {
userService service.UserService
activityService service.UserActivityService // 用户活跃统计服务
jwtService *service.JWTService
logService *service.LogService
}
// NewUserHandler 创建用户处理器
@@ -27,6 +30,28 @@ func NewUserHandler(userService service.UserService, activityService service.Use
}
}
// SetJWTService 设置JWT服务
func (h *UserHandler) SetJWTService(jwtService *service.JWTService) {
h.jwtService = jwtService
}
// SetActivityService 设置活跃统计服务
func (h *UserHandler) SetActivityService(activityService service.UserActivityService) {
h.activityService = activityService
}
// SetLogService 设置日志服务
func (h *UserHandler) SetLogService(logService *service.LogService) {
h.logService = logService
}
// generateTokenID 生成Token ID
func generateTokenID(token string) string {
h := sha256.New()
h.Write([]byte(token))
return fmt.Sprintf("%x", h.Sum(nil))[:16]
}
// Register 用户注册
func (h *UserHandler) Register(c *gin.Context) {
type RegisterRequest struct {
@@ -54,6 +79,35 @@ func (h *UserHandler) Register(c *gin.Context) {
accessToken, _ := h.jwtService.GenerateAccessToken(user.ID, user.Username)
refreshToken, _ := h.jwtService.GenerateRefreshToken(user.ID, user.Username)
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 记录用户活跃
if h.activityService != nil {
go func() {
ctx := context.Background()
loginType := model.LoginTypeLogin
h.activityService.RecordUserActive(ctx, user.ID, loginType, ip, userAgent)
}()
}
// 记录注册后首次登录
if h.logService != nil {
go func() {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
LoginType: string(model.LoginTypePassword),
Event: string(model.LoginEventLogin),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
TokenID: generateTokenID(refreshToken),
})
}()
}
response.Success(c, gin.H{
"user": dto.ConvertUserToResponse(user),
"token": accessToken,
@@ -94,17 +148,36 @@ func (h *UserHandler) Login(c *gin.Context) {
accessToken, _ := h.jwtService.GenerateAccessToken(user.ID, user.Username)
refreshToken, _ := h.jwtService.GenerateRefreshToken(user.ID, user.Username)
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 记录用户活跃
if h.activityService != nil {
go func() {
ctx := context.Background()
loginType := model.LoginTypeLogin
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
h.activityService.RecordUserActive(ctx, user.ID, loginType, ip, userAgent)
}()
}
// 更新登录日志补充IP和UserAgent
if h.logService != nil {
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
go func() {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
LoginType: string(model.LoginTypePassword),
Event: string(model.LoginEventLogin),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
})
}()
}
response.Success(c, gin.H{
"user": dto.ConvertUserToResponse(user),
"token": accessToken,
@@ -364,18 +437,43 @@ func (h *UserHandler) RefreshToken(c *gin.Context) {
return
}
// 获取用户信息
user, err := h.userService.GetUserByID(c.Request.Context(), claims.UserID)
if err != nil {
response.InternalServerError(c, "user not found")
return
}
// 生成新 token
accessToken, _ := h.jwtService.GenerateAccessToken(claims.UserID, claims.Username)
refreshToken, _ := h.jwtService.GenerateRefreshToken(claims.UserID, claims.Username)
accessToken, _ := h.jwtService.GenerateAccessToken(user.ID, user.Username)
refreshToken, _ := h.jwtService.GenerateRefreshToken(user.ID, user.Username)
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 记录用户活跃
if h.activityService != nil {
go func() {
ctx := context.Background()
loginType := model.LoginTypeRefresh
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
h.activityService.RecordUserActive(ctx, claims.UserID, loginType, ip, userAgent)
h.activityService.RecordUserActive(ctx, user.ID, loginType, ip, userAgent)
}()
}
// 记录Token刷新日志
if h.logService != nil {
go func() {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
LoginType: string(model.LoginTypePassword),
Event: string(model.LoginEventRefresh),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
TokenID: generateTokenID(req.RefreshToken),
})
}()
}
@@ -385,16 +483,6 @@ func (h *UserHandler) RefreshToken(c *gin.Context) {
})
}
// SetJWTService 设置JWT服务
func (h *UserHandler) SetJWTService(jwtSvc *service.JWTService) {
h.jwtService = jwtSvc
}
// SetActivityService 设置活跃统计服务
func (h *UserHandler) SetActivityService(activityService service.UserActivityService) {
h.activityService = activityService
}
// FollowUser 关注用户
func (h *UserHandler) FollowUser(c *gin.Context) {
userID := c.Param("id")
@@ -657,6 +745,40 @@ func (h *UserHandler) SendChangePasswordCode(c *gin.Context) {
response.Success(c, gin.H{"success": true})
}
// Logout 用户登出
func (h *UserHandler) Logout(c *gin.Context) {
currentUserID := c.GetString("user_id")
if currentUserID == "" {
response.Unauthorized(c, "not logged in")
return
}
ip := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
// 获取用户信息
user, err := h.userService.GetUserByID(c.Request.Context(), currentUserID)
if err != nil {
response.InternalServerError(c, "user not found")
return
}
// 记录登出日志
if h.logService != nil {
h.logService.LoginLog.RecordLogin(&model.LoginLog{
UserID: user.ID,
UserName: user.Username,
NickName: user.Nickname,
Event: string(model.LoginEventLogout),
IP: ip,
UserAgent: userAgent,
Result: string(model.LoginResultSuccess),
})
}
response.Success(c, gin.H{"success": true})
}
// Search 搜索用户
func (h *UserHandler) Search(c *gin.Context) {
keyword := c.Query("keyword")