| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- 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)
- }
|