package controllers import ( "Tech/common" "encoding/json" "os" "path/filepath" "strings" beego "github.com/beego/beego/v2/server/web" "github.com/sirupsen/logrus" ) type LogController struct { beego.Controller } // LogList 获取日志文件列表 func (c *LogController) LogList() { // 验证用户权限 userName := c.Ctx.Input.Header("userName") if userName == "" { c.Ctx.Output.SetStatus(401) c.Data["json"] = map[string]interface{}{ "code": 401, "msg": "未提供认证信息", } c.ServeJSON() return } // 记录操作日志 clientIP := c.Ctx.Input.IP() common.LogUserAction("", userName, "log_list", "system", logrus.Fields{ "ip": clientIP, }) // 获取日志文件信息 logFiles, err := common.GetLogFilesInfo() if err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]interface{}{ "code": 500, "msg": "获取日志文件信息失败: " + err.Error(), } c.ServeJSON() return } c.Data["json"] = map[string]interface{}{ "code": 200, "data": logFiles, "msg": "获取成功", } c.ServeJSON() } // LogView 查看日志文件内容 func (c *LogController) LogView() { // 验证用户权限 userName := c.Ctx.Input.Header("userName") if userName == "" { c.Ctx.Output.SetStatus(401) c.Data["json"] = map[string]interface{}{ "code": 401, "msg": "未提供认证信息", } c.ServeJSON() return } type LogViewRequest struct { FileName string `json:"fileName"` Lines int `json:"lines"` // 读取的行数,默认100 Offset int `json:"offset"` // 从末尾开始的偏移量,默认0 } var req LogViewRequest if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "请求参数格式错误: " + err.Error(), } c.ServeJSON() return } // 设置默认值 if req.Lines <= 0 { req.Lines = 100 } // 验证文件名,防止路径遍历攻击 if strings.Contains(req.FileName, "..") || strings.Contains(req.FileName, "/") || strings.Contains(req.FileName, "\\") { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "非法的文件名", } c.ServeJSON() return } // 检查文件是否是日志文件 if !strings.HasPrefix(req.FileName, "info") && !strings.HasPrefix(req.FileName, "warn") && !strings.HasPrefix(req.FileName, "error") || !strings.HasSuffix(req.FileName, ".log") && !strings.HasSuffix(req.FileName, ".log.gz") { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "非日志文件", } c.ServeJSON() return } // 构建文件完整路径 logDir := filepath.Join(".", "log") filePath := filepath.Join(logDir, req.FileName) // 检查文件是否存在 if _, err := os.Stat(filePath); os.IsNotExist(err) { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]interface{}{ "code": 404, "msg": "文件不存在", } c.ServeJSON() return } // 读取文件内容 var content string var err error if strings.HasSuffix(req.FileName, ".gz") { // 压缩文件需要特殊处理,这里简化处理,返回提示信息 content = "压缩文件暂不支持在线查看,请下载后查看" } else { content, err = readLastLines(filePath, req.Lines, req.Offset) if err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]interface{}{ "code": 500, "msg": "读取文件失败: " + err.Error(), } c.ServeJSON() return } } // 记录操作日志 clientIP := c.Ctx.Input.IP() common.LogUserAction("", userName, "log_view", "system", logrus.Fields{ "ip": clientIP, "file_name": req.FileName, "lines": req.Lines, "offset": req.Offset, }) c.Data["json"] = map[string]interface{}{ "code": 200, "data": map[string]interface{}{ "content": content, "file": req.FileName, }, "msg": "获取成功", } c.ServeJSON() } // LogDownload 下载日志文件 func (c *LogController) LogDownload() { // 验证用户权限 userName := c.Ctx.Input.Header("userName") if userName == "" { c.Ctx.Output.SetStatus(401) c.Data["json"] = map[string]interface{}{ "code": 401, "msg": "未提供认证信息", } c.ServeJSON() return } fileName := c.GetString("fileName") if fileName == "" { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "文件名不能为空", } c.ServeJSON() return } // 验证文件名,防止路径遍历攻击 if strings.Contains(fileName, "..") || strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "非法的文件名", } c.ServeJSON() return } // 检查文件是否是日志文件 if !strings.HasPrefix(fileName, "info") && !strings.HasPrefix(fileName, "warn") && !strings.HasPrefix(fileName, "error") || !strings.HasSuffix(fileName, ".log") && !strings.HasSuffix(fileName, ".log.gz") { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "非日志文件", } c.ServeJSON() return } // 构建文件完整路径 logDir := filepath.Join(".", "log") filePath := filepath.Join(logDir, fileName) // 检查文件是否存在 if _, err := os.Stat(filePath); os.IsNotExist(err) { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]interface{}{ "code": 404, "msg": "文件不存在", } c.ServeJSON() return } // 记录操作日志 clientIP := c.Ctx.Input.IP() common.LogUserAction("", userName, "log_download", "system", logrus.Fields{ "ip": clientIP, "file_name": fileName, }) // 设置下载响应头 c.Ctx.Output.Download(filePath, fileName) } // LogLevelSet 设置日志级别 func (c *LogController) LogLevelSet() { // 验证用户权限 userName := c.Ctx.Input.Header("userName") if userName == "" { c.Ctx.Output.SetStatus(401) c.Data["json"] = map[string]interface{}{ "code": 401, "msg": "未提供认证信息", } c.ServeJSON() return } type LogLevelRequest struct { Level string `json:"level"` } var req LogLevelRequest if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "请求参数格式错误: " + err.Error(), } c.ServeJSON() return } // 验证日志级别 validLevels := map[string]bool{ "trace": true, "debug": true, "info": true, "warn": true, "error": true, "fatal": true, "panic": true, } if !validLevels[req.Level] { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "无效的日志级别", } c.ServeJSON() return } // 设置日志级别 err := common.SetLogLevel(req.Level) if err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]interface{}{ "code": 500, "msg": "设置日志级别失败: " + err.Error(), } c.ServeJSON() return } // 记录操作日志 clientIP := c.Ctx.Input.IP() common.LogUserAction("", userName, "log_level_set", "system", logrus.Fields{ "ip": clientIP, "level": req.Level, }) c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "设置成功", } c.ServeJSON() } // LogDelete 删除日志文件 func (c *LogController) LogDelete() { // 验证用户权限 userName := c.Ctx.Input.Header("userName") if userName == "" { c.Ctx.Output.SetStatus(401) c.Data["json"] = map[string]interface{}{ "code": 401, "msg": "未提供认证信息", } c.ServeJSON() return } type LogDeleteRequest struct { FileName string `json:"fileName"` } var req LogDeleteRequest if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "请求参数格式错误: " + err.Error(), } c.ServeJSON() return } // 验证文件名,防止路径遍历攻击 if strings.Contains(req.FileName, "..") || strings.Contains(req.FileName, "/") || strings.Contains(req.FileName, "\\") { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "非法的文件名", } c.ServeJSON() return } // 检查文件是否是日志文件 if !strings.HasPrefix(req.FileName, "info") && !strings.HasPrefix(req.FileName, "warn") && !strings.HasPrefix(req.FileName, "error") || !strings.HasSuffix(req.FileName, ".log") && !strings.HasSuffix(req.FileName, ".log.gz") { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]interface{}{ "code": 400, "msg": "非日志文件", } c.ServeJSON() return } // 构建文件完整路径 logDir := filepath.Join(".", "log") filePath := filepath.Join(logDir, req.FileName) // 检查文件是否存在 if _, err := os.Stat(filePath); os.IsNotExist(err) { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]interface{}{ "code": 404, "msg": "文件不存在", } c.ServeJSON() return } // 删除文件 err := os.Remove(filePath) if err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]interface{}{ "code": 500, "msg": "删除文件失败: " + err.Error(), } c.ServeJSON() return } // 记录操作日志 clientIP := c.Ctx.Input.IP() common.LogUserAction("", userName, "log_delete", "system", logrus.Fields{ "ip": clientIP, "file_name": req.FileName, }) c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "删除成功", } c.ServeJSON() } // readLastLines 读取文件最后N行 func readLastLines(filePath string, lines int, offset int) (string, error) { // 读取文件内容 content, err := os.ReadFile(filePath) if err != nil { return "", err } // 按行分割 allLines := strings.Split(string(content), "\n") // 计算起始索引 startIndex := len(allLines) - lines - offset if startIndex < 0 { startIndex = 0 } // 截取需要的行 resultLines := allLines[startIndex : len(allLines)-offset] return strings.Join(resultLines, "\n"), nil }