package controllers import ( "encoding/json" "fmt" "math/rand" "shudao-chat-go/models" "shudao-chat-go/utils" "strconv" "strings" "time" "github.com/beego/beego/v2/server/web" ) // TrackingController 埋点记录控制器 type TrackingController struct { web.Controller } // TrackingRequest 埋点请求结构(user_id从token中获取) type TrackingRequest struct { ApiPath string `json:"api_path"` Method string `json:"method"` ExtraData string `json:"extra_data"` } // TrackingResponse 埋点响应结构 type TrackingResponse struct { Code int `json:"code"` Message string `json:"message"` Data struct { RequestId string `json:"request_id"` } `json:"data"` } // RecordTracking 记录埋点数据 func (c *TrackingController) RecordTracking() { // 从token中获取用户信息 userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo")) if err != nil { c.Data["json"] = map[string]interface{}{ "code": 401, "message": "获取用户信息失败: " + err.Error(), } c.ServeJSON() return } userID := int(userInfo.ID) if userID == 0 { userID = 1 } var req TrackingRequest err = json.Unmarshal(c.Ctx.Input.RequestBody, &req) if err != nil { c.Data["json"] = map[string]interface{}{ "code": 400, "message": "请求参数解析失败", } c.ServeJSON() return } if req.ApiPath == "" { c.Data["json"] = map[string]interface{}{ "code": 400, "message": "接口路径不能为空", } c.ServeJSON() return } // 生成请求ID requestId := c.generateRequestID() // 获取客户端IP ipAddress := c.getClientIP() // 获取User-Agent userAgent := c.Ctx.Input.Header("User-Agent") if userAgent == "" { userAgent = "Unknown" } // 通过接口路径查找接口名称 apiName := c.getApiNameByPath(req.ApiPath) // 创建埋点记录 trackingRecord := models.TrackingRecord{ UserID: userID, ApiPath: req.ApiPath, ApiName: apiName, Method: req.Method, UserAgent: userAgent, IpAddress: ipAddress, RequestId: requestId, Status: 1, Duration: 0, // 前端调用时暂时设为0,后续可以扩展 ExtraData: req.ExtraData, } // 保存到数据库 result := models.DB.Create(&trackingRecord) if result.Error != nil { c.Data["json"] = map[string]interface{}{ "code": 500, "message": "埋点记录保存失败: " + result.Error.Error(), } c.ServeJSON() return } // 返回成功响应 c.Data["json"] = TrackingResponse{ Code: 200, Message: "埋点记录成功", Data: struct { RequestId string `json:"request_id"` }{ RequestId: requestId, }, } c.ServeJSON() } // getClientIP 获取客户端真实IP func (c *TrackingController) getClientIP() string { // 优先从X-Forwarded-For获取 xForwardedFor := c.Ctx.Input.Header("X-Forwarded-For") if xForwardedFor != "" { ips := strings.Split(xForwardedFor, ",") if len(ips) > 0 { return strings.TrimSpace(ips[0]) } } // 从X-Real-IP获取 xRealIP := c.Ctx.Input.Header("X-Real-IP") if xRealIP != "" { return xRealIP } // 从RemoteAddr获取 remoteAddr := c.Ctx.Input.Context.Request.RemoteAddr if remoteAddr != "" { // 去掉端口号 if colonIndex := strings.LastIndex(remoteAddr, ":"); colonIndex != -1 { return remoteAddr[:colonIndex] } return remoteAddr } return "Unknown" } // getApiNameByPath 通过接口路径获取接口名称 func (c *TrackingController) getApiNameByPath(apiPath string) string { // 首先从数据库查找映射关系 var mapping models.ApiPathMapping result := models.DB.Where("api_path = ? AND status = 1", apiPath).First(&mapping) if result.Error == nil { return mapping.ApiName } // 如果数据库中没有找到,使用默认的路径解析规则 return c.parseApiNameFromPath(apiPath) } // parseApiNameFromPath 从路径解析接口名称 func (c *TrackingController) parseApiNameFromPath(apiPath string) string { // 移除前缀 /apiv1/ if strings.HasPrefix(apiPath, "/apiv1/") { apiPath = strings.TrimPrefix(apiPath, "/apiv1/") } // 移除开头的 / apiPath = strings.TrimPrefix(apiPath, "/") // 将路径转换为更友好的名称 parts := strings.Split(apiPath, "/") var nameParts []string for _, part := range parts { if part != "" { // 将下划线替换为空格,并转换为标题格式 part = strings.ReplaceAll(part, "_", " ") nameParts = append(nameParts, strings.Title(part)) } } if len(nameParts) > 0 { return strings.Join(nameParts, " ") } return "未知接口" } // GetTrackingRecords 获取埋点记录列表 func (c *TrackingController) GetTrackingRecords() { // 获取查询参数 userIDStr := c.GetString("user_id") apiPath := c.GetString("api_path") pageStr := c.GetString("page", "1") pageSizeStr := c.GetString("page_size", "20") // 转换分页参数 page, err := strconv.Atoi(pageStr) if err != nil || page < 1 { page = 1 } pageSize, err := strconv.Atoi(pageSizeStr) if err != nil || pageSize < 1 || pageSize > 100 { pageSize = 20 } // 构建查询条件 query := models.DB.Model(&models.TrackingRecord{}) if userIDStr != "" { userID, err := strconv.Atoi(userIDStr) if err == nil { query = query.Where("user_id = ?", userID) } } if apiPath != "" { query = query.Where("api_path LIKE ?", "%"+apiPath+"%") } // 获取总数 var total int64 query.Count(&total) // 分页查询 var records []models.TrackingRecord offset := (page - 1) * pageSize result := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&records) if result.Error != nil { c.Data["json"] = map[string]interface{}{ "code": 500, "message": "查询失败: " + result.Error.Error(), } c.ServeJSON() return } // 返回结果 c.Data["json"] = map[string]interface{}{ "code": 200, "message": "查询成功", "data": map[string]interface{}{ "list": records, "total": total, "page": page, "page_size": pageSize, }, } c.ServeJSON() } // AddApiMapping 添加接口路径映射 func (c *TrackingController) AddApiMapping() { var req struct { ApiPath string `json:"api_path"` ApiName string `json:"api_name"` ApiDesc string `json:"api_desc"` } err := json.Unmarshal(c.Ctx.Input.RequestBody, &req) if err != nil { c.Data["json"] = map[string]interface{}{ "code": 400, "message": "请求参数解析失败", } c.ServeJSON() return } // 验证必要参数 if req.ApiPath == "" || req.ApiName == "" { c.Data["json"] = map[string]interface{}{ "code": 400, "message": "接口路径和接口名称不能为空", } c.ServeJSON() return } // 检查是否已存在 var existingMapping models.ApiPathMapping result := models.DB.Where("api_path = ?", req.ApiPath).First(&existingMapping) if result.Error == nil { c.Data["json"] = map[string]interface{}{ "code": 400, "message": "该接口路径已存在映射", } c.ServeJSON() return } // 创建新映射 mapping := models.ApiPathMapping{ ApiPath: req.ApiPath, ApiName: req.ApiName, ApiDesc: req.ApiDesc, Status: 1, } result = models.DB.Create(&mapping) if result.Error != nil { c.Data["json"] = map[string]interface{}{ "code": 500, "message": "添加映射失败: " + result.Error.Error(), } c.ServeJSON() return } c.Data["json"] = map[string]interface{}{ "code": 200, "message": "添加映射成功", "data": mapping, } c.ServeJSON() } // GetApiMappings 获取接口路径映射列表 func (c *TrackingController) GetApiMappings() { var mappings []models.ApiPathMapping result := models.DB.Where("status = 1").Order("created_at DESC").Find(&mappings) if result.Error != nil { c.Data["json"] = map[string]interface{}{ "code": 500, "message": "查询失败: " + result.Error.Error(), } c.ServeJSON() return } c.Data["json"] = map[string]interface{}{ "code": 200, "message": "查询成功", "data": mappings, } c.ServeJSON() } // generateRequestID 生成请求ID func (c *TrackingController) generateRequestID() string { // 使用时间戳和随机数生成唯一ID timestamp := time.Now().UnixNano() randomNum := rand.Intn(10000) return fmt.Sprintf("%d_%d", timestamp, randomNum) }