Skip to content

Commit 2a3f1b7

Browse files
committed
开发api日志功能
1 parent dbc8fbe commit 2a3f1b7

File tree

9 files changed

+217
-8
lines changed

9 files changed

+217
-8
lines changed

config/logger.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"level": "info",
3+
"filename": "logs/app.log",
4+
"maxSize": 100,
5+
"maxBackups": 30,
6+
"maxAge": 7,
7+
"compress": true,
8+
"console": true
9+
}

internal/handler/api_log.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package handler
2+
3+
import (
4+
"ffly-baisc/internal/service"
5+
"ffly-baisc/pkg/response"
6+
"net/http"
7+
8+
"github.com/gin-gonic/gin"
9+
)
10+
11+
// GetApiLogList 获取用户列表
12+
func GetApiLogList(c *gin.Context) {
13+
var apiLogService service.ApiLogService
14+
15+
apiLogs, pagination, err := apiLogService.GetApiLogList(c)
16+
if err != nil {
17+
response.Error(c, http.StatusInternalServerError, "获取日志列表失败", err)
18+
return
19+
}
20+
21+
response.SuccessWithPagination(c, apiLogs, pagination, "日志列表获取成功")
22+
}

internal/middleware/api_log.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package middleware
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"strings"
7+
"time"
8+
9+
"ffly-baisc/internal/model"
10+
"ffly-baisc/internal/service"
11+
12+
"github.com/gin-gonic/gin"
13+
)
14+
15+
type responseBodyWriter struct {
16+
gin.ResponseWriter // 原始的响应写入器
17+
body *bytes.Buffer // 用于保存响应体的缓冲区
18+
}
19+
20+
func (w *responseBodyWriter) Write(b []byte) (int, error) {
21+
w.body.Write(b) // 将数据写入缓冲区
22+
return w.ResponseWriter.Write(b) // 将数据写入原始的 ResponseWriter
23+
}
24+
25+
// ApiLog 记录API访问日志
26+
func ApiLog() gin.HandlerFunc {
27+
return func(c *gin.Context) {
28+
// 获取请求体
29+
var reqBodyBytes []byte
30+
if c.Request.Body != nil {
31+
// 1. 一次性读取全部内容
32+
reqBodyBytes, _ = io.ReadAll(c.Request.Body)
33+
// 2. 创建新的缓冲区, 将已读取的请求体数据保存到一个可重复读取的缓冲区
34+
newReader := bytes.NewBuffer(reqBodyBytes)
35+
// 3. 替换原来的请求体
36+
c.Request.Body = io.NopCloser(newReader)
37+
}
38+
39+
responseBodyWriter := &responseBodyWriter{
40+
ResponseWriter: c.Writer, // 原始的 ResponseWriter
41+
body: &bytes.Buffer{}, // 初始化缓冲区
42+
}
43+
c.Writer = responseBodyWriter // 替换为自定义的 ResponseWriter
44+
45+
// 开始时间
46+
startTime := time.Now()
47+
48+
// 处理请求
49+
c.Next()
50+
51+
// 计算请求处理时间
52+
duration := time.Since(startTime)
53+
54+
var typeStr string
55+
if strings.Contains(c.Request.URL.Path, "/login") {
56+
typeStr = "login"
57+
} else {
58+
typeStr = "operate"
59+
}
60+
61+
// 创建日志记录
62+
apiLog := &model.ApiLog{
63+
UserID: c.GetUint("userID"),
64+
Username: c.GetString("username"),
65+
Method: c.Request.Method,
66+
Path: c.Request.URL.Path,
67+
Query: c.Request.URL.RawQuery,
68+
Body: string(reqBodyBytes),
69+
ResponseBody: responseBodyWriter.body.String(), // 获取响应体
70+
ClientIP: c.ClientIP(),
71+
UserAgent: c.Request.UserAgent(),
72+
Type: typeStr,
73+
StatusCode: c.Writer.Status(),
74+
Duration: duration.Milliseconds(), // 转换为毫秒
75+
}
76+
77+
// 异步保存日志
78+
go func(log *model.ApiLog) {
79+
var service service.ApiLogService
80+
// 创建日志
81+
if err := service.CreateApiLog(log); err != nil {
82+
// 记录日志失败
83+
}
84+
}(apiLog)
85+
}
86+
}

internal/model/api_log.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package model
2+
3+
type ApiLog struct {
4+
UserID uint `json:"userId"`
5+
Username string `json:"username"`
6+
Method string `json:"method"`
7+
Path string `json:"path"`
8+
Query string `json:"query"`
9+
Body string `json:"body"`
10+
UserAgent string `json:"userAgent"`
11+
ClientIP string `json:"clientIp"`
12+
StatusCode int `json:"statusCode"`
13+
Duration int64 `json:"duration"`
14+
ResponseBody string `json:"responseBody"`
15+
Type string `json:"type"` // operate: 操作日志, login: 登录日志
16+
BaseModel
17+
}
18+
19+
func (log *ApiLog) TableName() string {
20+
return "api_logs"
21+
}

internal/router/router.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ func InitRouter() {
1313
gin.SetMode(gin.DebugMode) // 设置运行模式
1414
r := gin.Default()
1515

16+
// 使用 ApiLog 中间件
17+
r.Use(middleware.ApiLog())
18+
1619
// API v1
1720
v1 := r.Group("/api/v1")
1821
{
@@ -32,6 +35,8 @@ func InitRouter() {
3235
routes.ResigterRoleRouter(authGroup)
3336
// 注册权限路由
3437
routes.ResigterPermissionRouter(authGroup)
38+
// 注册 API 日志 路由
39+
routes.ResigterApiLogRouter(authGroup)
3540
}
3641

3742
r.Run(fmt.Sprintf(":%d", config.GlobalConfig.App.Port)) // 监听端口

internal/router/routes/api_log.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package routes
2+
3+
import (
4+
"ffly-baisc/internal/handler"
5+
6+
"github.com/gin-gonic/gin"
7+
)
8+
9+
func ResigterApiLogRouter(g *gin.RouterGroup) {
10+
group := g.Group("/api_log")
11+
{
12+
group.GET("", handler.GetApiLogList)
13+
}
14+
}

internal/service/api_log.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package service
2+
3+
import (
4+
"ffly-baisc/internal/db"
5+
"ffly-baisc/internal/model"
6+
"ffly-baisc/pkg/pagination"
7+
8+
"github.com/gin-gonic/gin"
9+
)
10+
11+
type ApiLogService struct{}
12+
13+
// GetApiLogList 获取用户列表
14+
func (service *ApiLogService) GetApiLogList(c *gin.Context) ([]*model.ApiLog, *pagination.Pagination, error) {
15+
var apiLogs []*model.ApiLog
16+
17+
// 查询权限列表
18+
pagination, err := pagination.GetListByContext(db.DB.MySQL, &apiLogs, c)
19+
if err != nil {
20+
return nil, nil, err
21+
}
22+
23+
return apiLogs, pagination, nil
24+
}
25+
26+
// CreateApiLog 创建api日志
27+
func (service *ApiLogService) CreateApiLog(apiLog *model.ApiLog) error {
28+
if err := db.DB.MySQL.Create(apiLog).Error; err != nil {
29+
return err
30+
}
31+
32+
return nil
33+
}

schema.sql

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,32 @@ create table if not exists `role_permissions` (
114114
references `permissions` (`id`) on delete cascade on update cascade -- 引用 permissions.id 并设置级联删除和更新
115115
) engine=innodb auto_increment=1 comment='角色权限关联表';
116116

117+
-- 创建日志表
118+
create table if not exists `api_logs` (
119+
`id` bigint unsigned not null auto_increment comment 'ID',
120+
`user_id` bigint unsigned not null comment '用户id',
121+
`username` varchar(50) not null comment '用户名',
122+
`path` varchar(255) not null comment '请求路径',
123+
`method` varchar(10) not null comment '请求方法',
124+
`query` text default null comment '请求参数',
125+
`body` text default null comment '请求体',
126+
`user_agent` varchar(255) not null comment '用户代理',
127+
`client_ip` varchar(20) not null comment '客户端IP',
128+
`status_code` int not null comment '状态码',
129+
`duration` bigint not null comment '请求耗时(ms)',
130+
`response_body` text default null comment '响应体',
131+
`type` enum('operate', 'login') not null comment '日志类型, operate: 操作日志, login: 登录日志',
132+
`created_at` timestamp not null default current_timestamp comment '创建时间',
133+
`updated_at` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
134+
`deleted_at` timestamp null default null comment '删除时间',
135+
primary key (`id`)
136+
) engine=innodb auto_increment=1 comment='日志表';
117137

138+
-- -- 修改表
139+
-- alter table `users`
140+
-- add unique key `uk_username` (`username`),
141+
-- add unique key `uk_email` (`email`),
142+
-- add unique key `uk_phone` (`phone`);
118143

119-
-- 修改表
120-
alter table `users`
121-
add unique key `uk_username` (`username`),
122-
add unique key `uk_email` (`email`),
123-
add unique key `uk_phone` (`phone`);
124-
125-
alter table `permissions`
126-
modify column `type` enum('menu', 'button') not null comment '权限类型, menu: 菜单, button: 按钮';
144+
-- alter table `permissions`
145+
-- modify column `type` enum('menu', 'button') not null comment '权限类型, menu: 菜单, button: 按钮';

0 commit comments

Comments
 (0)