system_log_controller.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package controller
  2. import (
  3. "net/http"
  4. "strconv"
  5. "strings"
  6. "github.com/2930134478/AI-CS/backend/models"
  7. "github.com/2930134478/AI-CS/backend/repository"
  8. "github.com/2930134478/AI-CS/backend/service"
  9. "github.com/gin-gonic/gin"
  10. )
  11. type SystemLogController struct {
  12. logs *service.SystemLogService
  13. users *service.UserService
  14. appSettings *repository.AppSettingRepository
  15. }
  16. func NewSystemLogController(logs *service.SystemLogService, users *service.UserService, appSettings *repository.AppSettingRepository) *SystemLogController {
  17. return &SystemLogController{logs: logs, users: users, appSettings: appSettings}
  18. }
  19. // GetLogs 查询日志(客服端)。
  20. func (lc *SystemLogController) GetLogs(c *gin.Context) {
  21. if !requirePermission(c, lc.users, string(service.PermLogs)) {
  22. return
  23. }
  24. var convID *uint
  25. if v := strings.TrimSpace(c.Query("conversation_id")); v != "" {
  26. if id, err := strconv.ParseUint(v, 10, 64); err == nil {
  27. t := uint(id)
  28. convID = &t
  29. }
  30. }
  31. page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
  32. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50"))
  33. res, err := lc.logs.Query(service.QuerySystemLogsInput{
  34. From: c.Query("from"),
  35. To: c.Query("to"),
  36. Level: c.Query("level"),
  37. Category: c.Query("category"),
  38. Event: c.Query("event"),
  39. Source: c.Query("source"),
  40. ConversationID: convID,
  41. Keyword: c.Query("keyword"),
  42. Page: page,
  43. PageSize: pageSize,
  44. })
  45. if err != nil {
  46. c.JSON(http.StatusInternalServerError, gin.H{"error": "查询日志失败"})
  47. return
  48. }
  49. c.JSON(http.StatusOK, res)
  50. }
  51. type reportFrontendLogRequest struct {
  52. Level string `json:"level"`
  53. Category string `json:"category"`
  54. Event string `json:"event"`
  55. TraceID string `json:"trace_id"`
  56. ConversationID *uint `json:"conversation_id"`
  57. VisitorID *uint `json:"visitor_id"`
  58. Message string `json:"message"`
  59. Meta map[string]interface{} `json:"meta"`
  60. }
  61. // ReportFrontendLog 前端上报日志(用于捕获页面异常与关键请求失败)。
  62. func (lc *SystemLogController) ReportFrontendLog(c *gin.Context) {
  63. var req reportFrontendLogRequest
  64. if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.Message) == "" {
  65. c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"})
  66. return
  67. }
  68. userID := getUserIDFromHeader(c)
  69. var pUserID *uint
  70. if userID > 0 {
  71. pUserID = &userID
  72. }
  73. // 基础防护:限制 message/meta 体量,避免日志接口被刷爆。
  74. if len(req.Message) > 2000 {
  75. req.Message = req.Message[:2000]
  76. }
  77. traceID := strings.TrimSpace(req.TraceID)
  78. if traceID == "" {
  79. traceID = getTraceID(c)
  80. }
  81. if req.Meta != nil {
  82. req.Meta["truncated"] = false
  83. }
  84. if err := lc.logs.Create(service.CreateSystemLogInput{
  85. Level: req.Level,
  86. Category: req.Category,
  87. Event: req.Event,
  88. Source: "frontend",
  89. TraceID: traceID,
  90. ConversationID: req.ConversationID,
  91. UserID: pUserID,
  92. VisitorID: req.VisitorID,
  93. Message: req.Message,
  94. Meta: req.Meta,
  95. }); err != nil {
  96. c.JSON(http.StatusInternalServerError, gin.H{"error": "写入日志失败"})
  97. return
  98. }
  99. c.JSON(http.StatusOK, gin.H{"ok": true})
  100. }
  101. // GetLogMinLevel 返回当前生效的落库级别、环境变量默认值、是否由数据库覆盖。
  102. func (lc *SystemLogController) GetLogMinLevel(c *gin.Context) {
  103. if !requirePermission(c, lc.users, string(service.PermLogs)) {
  104. return
  105. }
  106. envRank := service.SystemLogMinPersistLevelFromEnv()
  107. effective := lc.logs.MinPersistLevelRank()
  108. var persisted bool
  109. if lc.appSettings != nil {
  110. if row, err := lc.appSettings.Get(models.AppSettingKeySystemLogMinLevel); err == nil && row != nil && strings.TrimSpace(row.Value) != "" {
  111. persisted = true
  112. }
  113. }
  114. c.JSON(http.StatusOK, gin.H{
  115. "effective_min_level": service.SystemLogMinLevelLabel(effective),
  116. "env_min_level": service.SystemLogMinLevelLabel(envRank),
  117. "persisted_in_database": persisted,
  118. })
  119. }
  120. type putLogMinLevelBody struct {
  121. MinLevel string `json:"min_level"`
  122. }
  123. // PutLogMinLevel 将最低落库级别写入数据库并立即生效(覆盖 .env,直至删除该配置)。
  124. func (lc *SystemLogController) PutLogMinLevel(c *gin.Context) {
  125. if !requirePermission(c, lc.users, string(service.PermLogs)) {
  126. return
  127. }
  128. if lc.appSettings == nil {
  129. c.JSON(http.StatusInternalServerError, gin.H{"error": "配置存储不可用"})
  130. return
  131. }
  132. var body putLogMinLevelBody
  133. if err := c.ShouldBindJSON(&body); err != nil {
  134. c.JSON(http.StatusBadRequest, gin.H{"error": "请求体无效"})
  135. return
  136. }
  137. rank, err := service.ParseSystemLogMinPersistLevelStrict(body.MinLevel)
  138. if err != nil {
  139. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  140. return
  141. }
  142. label := service.SystemLogMinLevelLabel(rank)
  143. if err := lc.appSettings.SetValue(models.AppSettingKeySystemLogMinLevel, label); err != nil {
  144. c.JSON(http.StatusInternalServerError, gin.H{"error": "保存配置失败"})
  145. return
  146. }
  147. lc.logs.SetMinPersistLevelRank(rank)
  148. c.JSON(http.StatusOK, gin.H{
  149. "ok": true,
  150. "effective_min_level": label,
  151. })
  152. }
  153. // DeleteLogMinLevel 删除数据库中的覆盖项,恢复为环境变量 SYSTEM_LOG_MIN_LEVEL。
  154. func (lc *SystemLogController) DeleteLogMinLevel(c *gin.Context) {
  155. if !requirePermission(c, lc.users, string(service.PermLogs)) {
  156. return
  157. }
  158. if lc.appSettings == nil {
  159. c.JSON(http.StatusInternalServerError, gin.H{"error": "配置存储不可用"})
  160. return
  161. }
  162. if err := lc.appSettings.Delete(models.AppSettingKeySystemLogMinLevel); err != nil {
  163. c.JSON(http.StatusInternalServerError, gin.H{"error": "删除配置失败"})
  164. return
  165. }
  166. envRank := service.SystemLogMinPersistLevelFromEnv()
  167. lc.logs.SetMinPersistLevelRank(envRank)
  168. c.JSON(http.StatusOK, gin.H{
  169. "ok": true,
  170. "effective_min_level": service.SystemLogMinLevelLabel(envRank),
  171. })
  172. }