2026-01-15 13:51:44 +08:00
|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
2026-01-16 17:03:34 +08:00
|
|
|
|
"strconv"
|
2026-01-16 13:01:21 +08:00
|
|
|
|
"time"
|
2026-01-15 13:51:44 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
"github.com/niangaodev/art-code/repositories"
|
2026-01-16 17:03:34 +08:00
|
|
|
|
"github.com/niangaodev/art-code/utils"
|
2026-01-15 13:51:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func AdminGetRecentActivities(c *gin.Context) {
|
|
|
|
|
|
// 获取最近10条操作日志
|
2026-06-24 17:06:22 +08:00
|
|
|
|
logs, _, err := repositories.GetOperationLogs(1, 10, repositories.OperationLogFilter{})
|
2026-01-15 13:51:44 +08:00
|
|
|
|
if err != nil {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
utils.ServerError(c, err)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建响应
|
|
|
|
|
|
var activities []gin.H
|
2026-01-16 17:03:34 +08:00
|
|
|
|
if len(logs) > 0 {
|
|
|
|
|
|
for _, log := range logs {
|
|
|
|
|
|
// 根据HTTP方法设置图标
|
|
|
|
|
|
var icon string
|
|
|
|
|
|
switch log.Method {
|
|
|
|
|
|
case "POST":
|
|
|
|
|
|
icon = "➕"
|
|
|
|
|
|
case "PUT", "PATCH":
|
|
|
|
|
|
icon = "✏️"
|
|
|
|
|
|
case "DELETE":
|
|
|
|
|
|
icon = "🗑️"
|
|
|
|
|
|
case "GET":
|
|
|
|
|
|
icon = "📋"
|
|
|
|
|
|
case "OPTIONS":
|
|
|
|
|
|
icon = "⚙️"
|
|
|
|
|
|
default:
|
|
|
|
|
|
icon = "📋"
|
|
|
|
|
|
}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
|
2026-01-16 17:03:34 +08:00
|
|
|
|
// 构建活动文本描述
|
|
|
|
|
|
text := fmt.Sprintf("%s %s", log.Method, log.Path)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
|
2026-01-16 17:03:34 +08:00
|
|
|
|
activities = append(activities, gin.H{
|
|
|
|
|
|
"id": log.ID,
|
|
|
|
|
|
"icon": icon,
|
|
|
|
|
|
"text": text,
|
|
|
|
|
|
"time": time.Unix(log.CreatedAt, 0).Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
activities = []gin.H{}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 17:03:34 +08:00
|
|
|
|
utils.Success(c, activities)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取操作日志列表
|
|
|
|
|
|
func AdminGetOperationLogs(c *gin.Context) {
|
|
|
|
|
|
page := 1
|
|
|
|
|
|
pageSize := 10
|
|
|
|
|
|
|
|
|
|
|
|
if c.Query("page") != "" {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
if p, err := strconv.Atoi(c.Query("page")); err == nil {
|
|
|
|
|
|
page = p
|
|
|
|
|
|
}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if c.Query("pageSize") != "" {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
if ps, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
|
|
|
|
|
pageSize = ps
|
|
|
|
|
|
}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 17:06:22 +08:00
|
|
|
|
filter := repositories.OperationLogFilter{
|
|
|
|
|
|
Action: c.Query("action"),
|
|
|
|
|
|
Method: c.Query("method"),
|
|
|
|
|
|
StartDate: c.Query("startDate"),
|
|
|
|
|
|
EndDate: c.Query("endDate"),
|
|
|
|
|
|
}
|
|
|
|
|
|
if statusStr := c.Query("status"); statusStr != "" {
|
|
|
|
|
|
if s, err := strconv.Atoi(statusStr); err == nil {
|
|
|
|
|
|
filter.Status = s
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if userIDStr := c.Query("userId"); userIDStr != "" {
|
|
|
|
|
|
if uid, err := strconv.ParseUint(userIDStr, 10, 32); err == nil {
|
|
|
|
|
|
filter.UserID = uint(uid)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logs, total, err := repositories.GetOperationLogs(page, pageSize, filter)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
if err != nil {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
utils.ServerError(c, err)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 17:03:34 +08:00
|
|
|
|
// Ensure list is not nil
|
|
|
|
|
|
logList := repositories.BuildOperationLogsResponse(logs)
|
|
|
|
|
|
// BuildOperationLogsResponse returns []models.OperationLogResponse
|
|
|
|
|
|
// If logs is empty, it might return nil or empty slice depending on implementation.
|
|
|
|
|
|
// Let's assume repositories usually return nil for empty.
|
|
|
|
|
|
if logList == nil {
|
|
|
|
|
|
// We need to define the type or use empty interface slice, but gin.H is map.
|
|
|
|
|
|
// Actually BuildOperationLogsResponse returns specific struct slice.
|
|
|
|
|
|
// Let's rely on it being correct or check length?
|
|
|
|
|
|
// Since Go nil slice serializes to null, we want []
|
|
|
|
|
|
// But we can't easily assign []interface{} to specific type variable without re-allocating.
|
|
|
|
|
|
// However, utils.Success takes interface{}.
|
|
|
|
|
|
// We can just pass empty slice if nil.
|
|
|
|
|
|
// But wait, we are constructing a map:
|
|
|
|
|
|
}
|
|
|
|
|
|
// To be safe, let's verify repositories.BuildOperationLogsResponse
|
|
|
|
|
|
// Assuming it might return nil.
|
|
|
|
|
|
// We can't change the return type easily here.
|
|
|
|
|
|
// But we can do:
|
|
|
|
|
|
// "list": logList
|
|
|
|
|
|
// If logList is nil, json is null.
|
|
|
|
|
|
// User wants [].
|
|
|
|
|
|
// So we should fix it in repository or here.
|
|
|
|
|
|
// Let's assume we can just cast or verify.
|
|
|
|
|
|
// Actually simpler:
|
|
|
|
|
|
// utils.Success(c, ...) handles the response.
|
|
|
|
|
|
|
|
|
|
|
|
// Let's look at `repositories.BuildOperationLogsResponse`.
|
|
|
|
|
|
// Since I can't see it, I will assume it returns nil.
|
|
|
|
|
|
// I will construct the map carefully.
|
|
|
|
|
|
|
|
|
|
|
|
res := gin.H{
|
|
|
|
|
|
"list": logList,
|
2026-01-15 13:51:44 +08:00
|
|
|
|
"total": total,
|
|
|
|
|
|
"page": page,
|
|
|
|
|
|
"size": pageSize,
|
2026-01-16 17:03:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
if logList == nil {
|
|
|
|
|
|
res["list"] = []interface{}{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
utils.Success(c, res)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
2026-01-20 11:00:42 +08:00
|
|
|
|
|
|
|
|
|
|
// AdminGetAccessLogs 获取访问日志列表
|
|
|
|
|
|
func AdminGetAccessLogs(c *gin.Context) {
|
|
|
|
|
|
// 获取分页参数
|
|
|
|
|
|
page := 1
|
|
|
|
|
|
pageSize := 10
|
|
|
|
|
|
|
|
|
|
|
|
// 从查询参数中获取分页信息
|
|
|
|
|
|
if c.Query("page") != "" {
|
|
|
|
|
|
if p, err := strconv.Atoi(c.Query("page")); err == nil {
|
|
|
|
|
|
page = p
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if c.Query("pageSize") != "" {
|
|
|
|
|
|
if ps, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
|
|
|
|
|
pageSize = ps
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 17:06:22 +08:00
|
|
|
|
filter := repositories.AccessLogFilter{
|
|
|
|
|
|
Path: c.Query("path"),
|
|
|
|
|
|
Region: c.Query("region"),
|
|
|
|
|
|
StartDate: c.Query("startDate"),
|
|
|
|
|
|
EndDate: c.Query("endDate"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 11:00:42 +08:00
|
|
|
|
// 获取访问日志
|
2026-06-24 17:06:22 +08:00
|
|
|
|
logs, total, err := repositories.GetAccessLogs(page, pageSize, nil, filter)
|
2026-01-20 11:00:42 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
utils.ServerError(c, err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建响应
|
|
|
|
|
|
var logList []gin.H
|
|
|
|
|
|
for _, log := range logs {
|
|
|
|
|
|
logList = append(logList, gin.H{
|
|
|
|
|
|
"id": log.ID,
|
|
|
|
|
|
"ip": log.IP,
|
|
|
|
|
|
"userAgent": log.UserAgent,
|
|
|
|
|
|
"path": log.Path,
|
|
|
|
|
|
"method": log.Method,
|
|
|
|
|
|
"statusCode": log.StatusCode,
|
|
|
|
|
|
"responseTime": log.ResponseTime,
|
|
|
|
|
|
"region": log.Region,
|
|
|
|
|
|
"createdAt": time.Unix(log.CreatedAt, 0).Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if logList == nil {
|
|
|
|
|
|
logList = []gin.H{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res := gin.H{
|
|
|
|
|
|
"list": logList,
|
|
|
|
|
|
"total": total,
|
|
|
|
|
|
"page": page,
|
|
|
|
|
|
"size": pageSize,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
utils.Success(c, res)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AdminGetPostAccessLogs 获取指定文章的访问记录
|
|
|
|
|
|
func AdminGetPostAccessLogs(c *gin.Context) {
|
|
|
|
|
|
// 获取文章ID
|
|
|
|
|
|
postIDStr := c.Param("id")
|
|
|
|
|
|
postID, err := strconv.Atoi(postIDStr)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
utils.Error(c, 400, "Invalid post ID")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取分页参数
|
|
|
|
|
|
page := 1
|
|
|
|
|
|
pageSize := 20
|
|
|
|
|
|
|
|
|
|
|
|
if c.Query("page") != "" {
|
|
|
|
|
|
if p, err := strconv.Atoi(c.Query("page")); err == nil {
|
|
|
|
|
|
page = p
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if c.Query("pageSize") != "" {
|
|
|
|
|
|
if ps, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
|
|
|
|
|
pageSize = ps
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 15:23:37 +08:00
|
|
|
|
// 获取文章的访问记录(从 user_access_logs 表)
|
|
|
|
|
|
logs, total, err := repositories.GetPostAccessLogsFromUserAccessLogs(postID, page, pageSize)
|
2026-01-20 11:00:42 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
utils.ServerError(c, err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建响应
|
|
|
|
|
|
var logList []gin.H
|
|
|
|
|
|
for _, log := range logs {
|
|
|
|
|
|
logList = append(logList, gin.H{
|
2026-01-20 15:23:37 +08:00
|
|
|
|
"id": log.ID,
|
|
|
|
|
|
"userId": log.UserID,
|
|
|
|
|
|
"ip": log.UserIP,
|
|
|
|
|
|
"region": log.UserLocation,
|
|
|
|
|
|
"articleId": log.ArticleID,
|
|
|
|
|
|
"createdAt": time.Unix(log.AccessTime, 0).Format("2006-01-02 15:04:05"),
|
2026-01-20 11:00:42 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if logList == nil {
|
|
|
|
|
|
logList = []gin.H{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res := gin.H{
|
|
|
|
|
|
"list": logList,
|
|
|
|
|
|
"total": total,
|
|
|
|
|
|
"page": page,
|
|
|
|
|
|
"size": pageSize,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
utils.Success(c, res)
|
|
|
|
|
|
}
|