tracking.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. package controllers
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "math/rand"
  6. "shudao-chat-go/models"
  7. "shudao-chat-go/utils"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "github.com/beego/beego/v2/server/web"
  12. )
  13. // TrackingController 埋点记录控制器
  14. type TrackingController struct {
  15. web.Controller
  16. }
  17. // TrackingRequest 埋点请求结构(user_id从token中获取)
  18. type TrackingRequest struct {
  19. ApiPath string `json:"api_path"`
  20. Method string `json:"method"`
  21. ExtraData string `json:"extra_data"`
  22. }
  23. // TrackingResponse 埋点响应结构
  24. type TrackingResponse struct {
  25. Code int `json:"code"`
  26. Message string `json:"message"`
  27. Data struct {
  28. RequestId string `json:"request_id"`
  29. } `json:"data"`
  30. }
  31. // RecordTracking 记录埋点数据
  32. func (c *TrackingController) RecordTracking() {
  33. // 从token中获取用户信息
  34. userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
  35. if err != nil {
  36. c.Data["json"] = map[string]interface{}{
  37. "code": 401,
  38. "message": "获取用户信息失败: " + err.Error(),
  39. }
  40. c.ServeJSON()
  41. return
  42. }
  43. userID := int(userInfo.ID)
  44. if userID == 0 {
  45. userID = 1
  46. }
  47. var req TrackingRequest
  48. err = json.Unmarshal(c.Ctx.Input.RequestBody, &req)
  49. if err != nil {
  50. c.Data["json"] = map[string]interface{}{
  51. "code": 400,
  52. "message": "请求参数解析失败",
  53. }
  54. c.ServeJSON()
  55. return
  56. }
  57. if req.ApiPath == "" {
  58. c.Data["json"] = map[string]interface{}{
  59. "code": 400,
  60. "message": "接口路径不能为空",
  61. }
  62. c.ServeJSON()
  63. return
  64. }
  65. // 生成请求ID
  66. requestId := c.generateRequestID()
  67. // 获取客户端IP
  68. ipAddress := c.getClientIP()
  69. // 获取User-Agent
  70. userAgent := c.Ctx.Input.Header("User-Agent")
  71. if userAgent == "" {
  72. userAgent = "Unknown"
  73. }
  74. // 通过接口路径查找接口名称
  75. apiName := c.getApiNameByPath(req.ApiPath)
  76. // 创建埋点记录
  77. trackingRecord := models.TrackingRecord{
  78. UserID: userID,
  79. ApiPath: req.ApiPath,
  80. ApiName: apiName,
  81. Method: req.Method,
  82. UserAgent: userAgent,
  83. IpAddress: ipAddress,
  84. RequestId: requestId,
  85. Status: 1,
  86. Duration: 0, // 前端调用时暂时设为0,后续可以扩展
  87. ExtraData: req.ExtraData,
  88. }
  89. // 保存到数据库
  90. result := models.DB.Create(&trackingRecord)
  91. if result.Error != nil {
  92. c.Data["json"] = map[string]interface{}{
  93. "code": 500,
  94. "message": "埋点记录保存失败: " + result.Error.Error(),
  95. }
  96. c.ServeJSON()
  97. return
  98. }
  99. // 返回成功响应
  100. c.Data["json"] = TrackingResponse{
  101. Code: 200,
  102. Message: "埋点记录成功",
  103. Data: struct {
  104. RequestId string `json:"request_id"`
  105. }{
  106. RequestId: requestId,
  107. },
  108. }
  109. c.ServeJSON()
  110. }
  111. // getClientIP 获取客户端真实IP
  112. func (c *TrackingController) getClientIP() string {
  113. // 优先从X-Forwarded-For获取
  114. xForwardedFor := c.Ctx.Input.Header("X-Forwarded-For")
  115. if xForwardedFor != "" {
  116. ips := strings.Split(xForwardedFor, ",")
  117. if len(ips) > 0 {
  118. return strings.TrimSpace(ips[0])
  119. }
  120. }
  121. // 从X-Real-IP获取
  122. xRealIP := c.Ctx.Input.Header("X-Real-IP")
  123. if xRealIP != "" {
  124. return xRealIP
  125. }
  126. // 从RemoteAddr获取
  127. remoteAddr := c.Ctx.Input.Context.Request.RemoteAddr
  128. if remoteAddr != "" {
  129. // 去掉端口号
  130. if colonIndex := strings.LastIndex(remoteAddr, ":"); colonIndex != -1 {
  131. return remoteAddr[:colonIndex]
  132. }
  133. return remoteAddr
  134. }
  135. return "Unknown"
  136. }
  137. // getApiNameByPath 通过接口路径获取接口名称
  138. func (c *TrackingController) getApiNameByPath(apiPath string) string {
  139. // 首先从数据库查找映射关系
  140. var mapping models.ApiPathMapping
  141. result := models.DB.Where("api_path = ? AND status = 1", apiPath).First(&mapping)
  142. if result.Error == nil {
  143. return mapping.ApiName
  144. }
  145. // 如果数据库中没有找到,使用默认的路径解析规则
  146. return c.parseApiNameFromPath(apiPath)
  147. }
  148. // parseApiNameFromPath 从路径解析接口名称
  149. func (c *TrackingController) parseApiNameFromPath(apiPath string) string {
  150. // 移除前缀 /apiv1/
  151. if strings.HasPrefix(apiPath, "/apiv1/") {
  152. apiPath = strings.TrimPrefix(apiPath, "/apiv1/")
  153. }
  154. // 移除开头的 /
  155. apiPath = strings.TrimPrefix(apiPath, "/")
  156. // 将路径转换为更友好的名称
  157. parts := strings.Split(apiPath, "/")
  158. var nameParts []string
  159. for _, part := range parts {
  160. if part != "" {
  161. // 将下划线替换为空格,并转换为标题格式
  162. part = strings.ReplaceAll(part, "_", " ")
  163. nameParts = append(nameParts, strings.Title(part))
  164. }
  165. }
  166. if len(nameParts) > 0 {
  167. return strings.Join(nameParts, " ")
  168. }
  169. return "未知接口"
  170. }
  171. // GetTrackingRecords 获取埋点记录列表
  172. func (c *TrackingController) GetTrackingRecords() {
  173. // 获取查询参数
  174. userIDStr := c.GetString("user_id")
  175. apiPath := c.GetString("api_path")
  176. pageStr := c.GetString("page", "1")
  177. pageSizeStr := c.GetString("page_size", "20")
  178. // 转换分页参数
  179. page, err := strconv.Atoi(pageStr)
  180. if err != nil || page < 1 {
  181. page = 1
  182. }
  183. pageSize, err := strconv.Atoi(pageSizeStr)
  184. if err != nil || pageSize < 1 || pageSize > 100 {
  185. pageSize = 20
  186. }
  187. // 构建查询条件
  188. query := models.DB.Model(&models.TrackingRecord{})
  189. if userIDStr != "" {
  190. userID, err := strconv.Atoi(userIDStr)
  191. if err == nil {
  192. query = query.Where("user_id = ?", userID)
  193. }
  194. }
  195. if apiPath != "" {
  196. query = query.Where("api_path LIKE ?", "%"+apiPath+"%")
  197. }
  198. // 获取总数
  199. var total int64
  200. query.Count(&total)
  201. // 分页查询
  202. var records []models.TrackingRecord
  203. offset := (page - 1) * pageSize
  204. result := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&records)
  205. if result.Error != nil {
  206. c.Data["json"] = map[string]interface{}{
  207. "code": 500,
  208. "message": "查询失败: " + result.Error.Error(),
  209. }
  210. c.ServeJSON()
  211. return
  212. }
  213. // 返回结果
  214. c.Data["json"] = map[string]interface{}{
  215. "code": 200,
  216. "message": "查询成功",
  217. "data": map[string]interface{}{
  218. "list": records,
  219. "total": total,
  220. "page": page,
  221. "page_size": pageSize,
  222. },
  223. }
  224. c.ServeJSON()
  225. }
  226. // AddApiMapping 添加接口路径映射
  227. func (c *TrackingController) AddApiMapping() {
  228. var req struct {
  229. ApiPath string `json:"api_path"`
  230. ApiName string `json:"api_name"`
  231. ApiDesc string `json:"api_desc"`
  232. }
  233. err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
  234. if err != nil {
  235. c.Data["json"] = map[string]interface{}{
  236. "code": 400,
  237. "message": "请求参数解析失败",
  238. }
  239. c.ServeJSON()
  240. return
  241. }
  242. // 验证必要参数
  243. if req.ApiPath == "" || req.ApiName == "" {
  244. c.Data["json"] = map[string]interface{}{
  245. "code": 400,
  246. "message": "接口路径和接口名称不能为空",
  247. }
  248. c.ServeJSON()
  249. return
  250. }
  251. // 检查是否已存在
  252. var existingMapping models.ApiPathMapping
  253. result := models.DB.Where("api_path = ?", req.ApiPath).First(&existingMapping)
  254. if result.Error == nil {
  255. c.Data["json"] = map[string]interface{}{
  256. "code": 400,
  257. "message": "该接口路径已存在映射",
  258. }
  259. c.ServeJSON()
  260. return
  261. }
  262. // 创建新映射
  263. mapping := models.ApiPathMapping{
  264. ApiPath: req.ApiPath,
  265. ApiName: req.ApiName,
  266. ApiDesc: req.ApiDesc,
  267. Status: 1,
  268. }
  269. result = models.DB.Create(&mapping)
  270. if result.Error != nil {
  271. c.Data["json"] = map[string]interface{}{
  272. "code": 500,
  273. "message": "添加映射失败: " + result.Error.Error(),
  274. }
  275. c.ServeJSON()
  276. return
  277. }
  278. c.Data["json"] = map[string]interface{}{
  279. "code": 200,
  280. "message": "添加映射成功",
  281. "data": mapping,
  282. }
  283. c.ServeJSON()
  284. }
  285. // GetApiMappings 获取接口路径映射列表
  286. func (c *TrackingController) GetApiMappings() {
  287. var mappings []models.ApiPathMapping
  288. result := models.DB.Where("status = 1").Order("created_at DESC").Find(&mappings)
  289. if result.Error != nil {
  290. c.Data["json"] = map[string]interface{}{
  291. "code": 500,
  292. "message": "查询失败: " + result.Error.Error(),
  293. }
  294. c.ServeJSON()
  295. return
  296. }
  297. c.Data["json"] = map[string]interface{}{
  298. "code": 200,
  299. "message": "查询成功",
  300. "data": mappings,
  301. }
  302. c.ServeJSON()
  303. }
  304. // generateRequestID 生成请求ID
  305. func (c *TrackingController) generateRequestID() string {
  306. // 使用时间戳和随机数生成唯一ID
  307. timestamp := time.Now().UnixNano()
  308. randomNum := rand.Intn(10000)
  309. return fmt.Sprintf("%d_%d", timestamp, randomNum)
  310. }