mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-28 01:36:56 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			218 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package middleware
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"compress/gzip"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/1Panel-dev/1Panel/cmd/server/docs"
 | |
| 	"github.com/1Panel-dev/1Panel/core/app/model"
 | |
| 	"github.com/1Panel-dev/1Panel/core/app/service"
 | |
| 	"github.com/1Panel-dev/1Panel/core/constant"
 | |
| 	"github.com/1Panel-dev/1Panel/core/global"
 | |
| 	"github.com/1Panel-dev/1Panel/core/utils/copier"
 | |
| 	"github.com/gin-gonic/gin"
 | |
| )
 | |
| 
 | |
| func OperationLog() gin.HandlerFunc {
 | |
| 	return func(c *gin.Context) {
 | |
| 		if strings.Contains(c.Request.URL.Path, "search") || c.Request.Method == http.MethodGet {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		source := loadLogInfo(c.Request.URL.Path)
 | |
| 		pathItem := strings.TrimPrefix(c.Request.URL.Path, "/api/v2")
 | |
| 		pathItem = strings.TrimPrefix(pathItem, "/api/v2/core")
 | |
| 		record := &model.OperationLog{
 | |
| 			Source:    source,
 | |
| 			IP:        c.ClientIP(),
 | |
| 			Method:    strings.ToLower(c.Request.Method),
 | |
| 			Path:      pathItem,
 | |
| 			UserAgent: c.Request.UserAgent(),
 | |
| 		}
 | |
| 		var (
 | |
| 			swagger      swaggerJson
 | |
| 			operationDic operationJson
 | |
| 		)
 | |
| 		if err := json.Unmarshal(docs.XLogJson, &swagger); err != nil {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 		path, hasPath := swagger.Paths[record.Path]
 | |
| 		if !hasPath {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 		methodMap, isMethodMap := path.(map[string]interface{})
 | |
| 		if !isMethodMap {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 		dataMap, hasPost := methodMap["post"]
 | |
| 		if !hasPost {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 		data, isDataMap := dataMap.(map[string]interface{})
 | |
| 		if !isDataMap {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 		xlog, hasXlog := data["x-panel-log"]
 | |
| 		if !hasXlog {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 		if err := copier.Copy(&operationDic, xlog); err != nil {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 		if len(operationDic.FormatZH) == 0 {
 | |
| 			c.Next()
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		formatMap := make(map[string]interface{})
 | |
| 		if len(operationDic.BodyKeys) != 0 {
 | |
| 			body, err := io.ReadAll(c.Request.Body)
 | |
| 			if err == nil {
 | |
| 				c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
 | |
| 			}
 | |
| 			bodyMap := make(map[string]interface{})
 | |
| 			_ = json.Unmarshal(body, &bodyMap)
 | |
| 			for _, key := range operationDic.BodyKeys {
 | |
| 				if _, ok := bodyMap[key]; ok {
 | |
| 					formatMap[key] = bodyMap[key]
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if len(operationDic.BeforeFunctions) != 0 {
 | |
| 			for _, funcs := range operationDic.BeforeFunctions {
 | |
| 				for key, value := range formatMap {
 | |
| 					if funcs.InputValue == key {
 | |
| 						var names []string
 | |
| 						if funcs.IsList {
 | |
| 							sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
 | |
| 							_ = global.DB.Raw(sql, value).Scan(&names)
 | |
| 						} else {
 | |
| 							_ = global.DB.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names)
 | |
| 						}
 | |
| 						formatMap[funcs.OutputValue] = strings.Join(names, ",")
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		for key, value := range formatMap {
 | |
| 			if strings.Contains(operationDic.FormatEN, "["+key+"]") {
 | |
| 				if arrays, ok := value.([]string); ok {
 | |
| 					operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
 | |
| 					operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
 | |
| 				} else {
 | |
| 					operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", value))
 | |
| 					operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", value))
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		record.DetailEN = strings.ReplaceAll(operationDic.FormatEN, "[]", "")
 | |
| 		record.DetailZH = strings.ReplaceAll(operationDic.FormatZH, "[]", "")
 | |
| 
 | |
| 		writer := responseBodyWriter{
 | |
| 			ResponseWriter: c.Writer,
 | |
| 			body:           &bytes.Buffer{},
 | |
| 		}
 | |
| 		c.Writer = writer
 | |
| 		now := time.Now()
 | |
| 
 | |
| 		c.Next()
 | |
| 
 | |
| 		datas := writer.body.Bytes()
 | |
| 		if c.Request.Header.Get("Content-Encoding") == "gzip" {
 | |
| 			buf := bytes.NewReader(writer.body.Bytes())
 | |
| 			reader, err := gzip.NewReader(buf)
 | |
| 			if err != nil {
 | |
| 				record.Status = constant.StatusFailed
 | |
| 				record.Message = fmt.Sprintf("gzip new reader failed, err: %v", err)
 | |
| 				latency := time.Since(now)
 | |
| 				record.Latency = latency
 | |
| 
 | |
| 				if err := service.NewILogService().CreateOperationLog(record); err != nil {
 | |
| 					global.LOG.Errorf("create operation record failed, err: %v", err)
 | |
| 				}
 | |
| 				return
 | |
| 			}
 | |
| 			defer reader.Close()
 | |
| 			datas, _ = io.ReadAll(reader)
 | |
| 		}
 | |
| 		var res response
 | |
| 		_ = json.Unmarshal(datas, &res)
 | |
| 		if res.Code == 200 {
 | |
| 			record.Status = constant.StatusSuccess
 | |
| 		} else {
 | |
| 			record.Status = constant.StatusFailed
 | |
| 			record.Message = res.Message
 | |
| 		}
 | |
| 
 | |
| 		latency := time.Since(now)
 | |
| 		record.Latency = latency
 | |
| 
 | |
| 		if err := service.NewILogService().CreateOperationLog(record); err != nil {
 | |
| 			global.LOG.Errorf("create operation record failed, err: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type swaggerJson struct {
 | |
| 	Paths map[string]interface{} `json:"paths"`
 | |
| }
 | |
| 
 | |
| type operationJson struct {
 | |
| 	API             string         `json:"api"`
 | |
| 	Method          string         `json:"method"`
 | |
| 	BodyKeys        []string       `json:"bodyKeys"`
 | |
| 	ParamKeys       []string       `json:"paramKeys"`
 | |
| 	BeforeFunctions []functionInfo `json:"beforeFunctions"`
 | |
| 	FormatZH        string         `json:"formatZH"`
 | |
| 	FormatEN        string         `json:"formatEN"`
 | |
| }
 | |
| type functionInfo struct {
 | |
| 	InputColumn  string `json:"input_column"`
 | |
| 	InputValue   string `json:"input_value"`
 | |
| 	IsList       bool   `json:"isList"`
 | |
| 	DB           string `json:"db"`
 | |
| 	OutputColumn string `json:"output_column"`
 | |
| 	OutputValue  string `json:"output_value"`
 | |
| }
 | |
| 
 | |
| type response struct {
 | |
| 	Code    int    `json:"code"`
 | |
| 	Message string `json:"message"`
 | |
| }
 | |
| 
 | |
| type responseBodyWriter struct {
 | |
| 	gin.ResponseWriter
 | |
| 	body *bytes.Buffer
 | |
| }
 | |
| 
 | |
| func (r responseBodyWriter) Write(b []byte) (int, error) {
 | |
| 	r.body.Write(b)
 | |
| 	return r.ResponseWriter.Write(b)
 | |
| }
 | |
| 
 | |
| func loadLogInfo(path string) string {
 | |
| 	path = strings.ReplaceAll(path, "/api/v2", "")
 | |
| 	if !strings.Contains(path, "/") {
 | |
| 		return ""
 | |
| 	}
 | |
| 	pathArrays := strings.Split(path, "/")
 | |
| 	if len(pathArrays) < 2 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	return pathArrays[1]
 | |
| }
 |