log.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. package admin
  2. import (
  3. "context"
  4. "fmt"
  5. "math"
  6. "strings"
  7. "time"
  8. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  9. admin "github.com/go-nunu/nunu-layout-advanced/api/v1/admin"
  10. "github.com/go-nunu/nunu-layout-advanced/internal/model"
  11. "github.com/go-nunu/nunu-layout-advanced/internal/repository"
  12. "github.com/go-nunu/nunu-layout-advanced/pkg/sharding"
  13. "gorm.io/gorm"
  14. )
  15. type LogRepository interface {
  16. GetLog(ctx context.Context, id int64) (*model.Log, error)
  17. GetLogList(ctx context.Context, req admin.SearchLogParams) (*v1.PaginatedResponse[model.Log], error)
  18. }
  19. func NewLogRepository(
  20. repository *repository.Repository,
  21. ) LogRepository {
  22. return &logRepository{
  23. Repository: repository,
  24. }
  25. }
  26. type logRepository struct {
  27. *repository.Repository
  28. }
  29. func (r *logRepository) GetLog(ctx context.Context, id int64) (*model.Log, error) {
  30. var res model.Log
  31. // 获取分表管理器
  32. shardingMgr := r.getShardingManager()
  33. // 获取存在的分表
  34. existingTables := shardingMgr.GetTableNamesWithExistenceCheck(r.DBWithName(ctx, "admin"), "log", nil, nil)
  35. // 在各个分表中查找
  36. for _, tableName := range existingTables {
  37. err := r.DBWithName(ctx, "admin").Table(tableName).Where("id = ?", id).First(&res).Error
  38. if err == nil {
  39. res.SetTableName(tableName)
  40. return &res, nil
  41. }
  42. }
  43. return nil, fmt.Errorf("未找到ID为 %d 的日志记录", id)
  44. }
  45. func (r *logRepository) GetLogList(ctx context.Context, req admin.SearchLogParams) (*v1.PaginatedResponse[model.Log], error) {
  46. // 获取分表管理器
  47. shardingMgr := r.getShardingManager()
  48. // 解析时间范围(如果有的话)
  49. var startTime, endTime *time.Time
  50. // TODO: 这里可以根据req中的时间字段来确定查询范围
  51. // 暂时查询最近3个月的数据
  52. // 获取需要查询的表
  53. existingTables := shardingMgr.GetTableNamesWithExistenceCheck(r.DBWithName(ctx, "admin"), "log", startTime, endTime)
  54. if len(existingTables) == 0 {
  55. // 没有分表,返回空结果
  56. return &v1.PaginatedResponse[model.Log]{
  57. Records: []model.Log{},
  58. Page: 1,
  59. PageSize: 10,
  60. Total: 0,
  61. TotalPages: 0,
  62. }, nil
  63. }
  64. if len(existingTables) == 1 {
  65. // 只有一个表,直接查询
  66. return r.queryFromSingleTable(ctx, req, existingTables[0])
  67. }
  68. // 跨表分页查询
  69. return r.queryFromMultipleTables(ctx, req, existingTables)
  70. }
  71. // queryFromSingleTable 单表查询
  72. func (r *logRepository) queryFromSingleTable(ctx context.Context, req admin.SearchLogParams, tableName string) (*v1.PaginatedResponse[model.Log], error) {
  73. var res []model.Log
  74. var total int64
  75. query := r.DBWithName(ctx, "admin").Table(tableName)
  76. query = r.applyFilters(query, req)
  77. if err := query.Count(&total).Error; err != nil {
  78. return nil, err
  79. }
  80. page := req.Current
  81. pageSize := req.PageSize
  82. if page <= 0 {
  83. page = 1
  84. }
  85. if pageSize <= 0 {
  86. pageSize = 10
  87. } else if pageSize > 100 {
  88. pageSize = 100
  89. }
  90. offset := (page - 1) * pageSize
  91. if req.Column != "" {
  92. query = query.Order(req.Column + " " + req.Order)
  93. }
  94. result := query.Offset(offset).Limit(pageSize).Find(&res)
  95. if result.Error != nil {
  96. return nil, result.Error
  97. }
  98. return &v1.PaginatedResponse[model.Log]{
  99. Records: res,
  100. Page: page,
  101. PageSize: pageSize,
  102. Total: total,
  103. TotalPages: int(math.Ceil(float64(total) / float64(pageSize))),
  104. }, nil
  105. }
  106. // queryFromMultipleTables 多表联合查询
  107. func (r *logRepository) queryFromMultipleTables(ctx context.Context, req admin.SearchLogParams, tableNames []string) (*v1.PaginatedResponse[model.Log], error) {
  108. var allResults []model.Log
  109. var totalCount int64
  110. // 先计算总数
  111. for _, tableName := range tableNames {
  112. var count int64
  113. query := r.DBWithName(ctx, "admin").Table(tableName)
  114. query = r.applyFilters(query, req)
  115. if err := query.Count(&count).Error; err != nil {
  116. return nil, err
  117. }
  118. totalCount += count
  119. }
  120. page := req.Current
  121. pageSize := req.PageSize
  122. if page <= 0 {
  123. page = 1
  124. }
  125. if pageSize <= 0 {
  126. pageSize = 10
  127. } else if pageSize > 100 {
  128. pageSize = 100
  129. }
  130. // 计算需要跳过的记录数
  131. offset := (page - 1) * pageSize
  132. limit := pageSize
  133. currentOffset := 0
  134. // 逐表查询直到获取足够的记录
  135. for _, tableName := range tableNames {
  136. if limit <= 0 {
  137. break
  138. }
  139. var tableCount int64
  140. countQuery := r.DBWithName(ctx, "admin").Table(tableName)
  141. countQuery = r.applyFilters(countQuery, req)
  142. if err := countQuery.Count(&tableCount).Error; err != nil {
  143. return nil, err
  144. }
  145. // 如果当前表的记录数不足以满足offset要求,跳过这个表
  146. if currentOffset+int(tableCount) <= offset {
  147. currentOffset += int(tableCount)
  148. continue
  149. }
  150. // 计算在当前表中的offset
  151. tableOffset := offset - currentOffset
  152. if tableOffset < 0 {
  153. tableOffset = 0
  154. }
  155. var tableResults []model.Log
  156. query := r.DBWithName(ctx, "admin").Table(tableName)
  157. query = r.applyFilters(query, req)
  158. if req.Column != "" {
  159. query = query.Order(req.Column + " " + req.Order)
  160. }
  161. err := query.Offset(tableOffset).Limit(limit).Find(&tableResults).Error
  162. if err != nil {
  163. return nil, err
  164. }
  165. // 设置表名
  166. for i := range tableResults {
  167. tableResults[i].SetTableName(tableName)
  168. }
  169. allResults = append(allResults, tableResults...)
  170. limit -= len(tableResults)
  171. currentOffset += int(tableCount)
  172. }
  173. return &v1.PaginatedResponse[model.Log]{
  174. Records: allResults,
  175. Page: page,
  176. PageSize: pageSize,
  177. Total: totalCount,
  178. TotalPages: int(math.Ceil(float64(totalCount) / float64(pageSize))),
  179. }, nil
  180. }
  181. // applyFilters 应用查询过滤条件
  182. func (r *logRepository) applyFilters(query *gorm.DB, req admin.SearchLogParams) *gorm.DB {
  183. if req.RequestIp != "" {
  184. trimmedName := strings.TrimSpace(req.RequestIp)
  185. query = query.Where("request_ip LIKE CONCAT('%', ?, '%')", trimmedName)
  186. }
  187. if req.Uid != 0 {
  188. query = query.Where("uid = ?", req.Uid)
  189. }
  190. if req.Api != "" {
  191. trimmedName := strings.TrimSpace(req.Api)
  192. query = query.Where("api LIKE CONCAT('%', ?, '%')", trimmedName)
  193. }
  194. if req.Message != "" {
  195. trimmedName := strings.TrimSpace(req.Message)
  196. query = query.Where("message LIKE CONCAT('%', ?, '%')", trimmedName)
  197. }
  198. if req.ExtraData != "" {
  199. trimmedName := strings.TrimSpace(req.ExtraData)
  200. query = query.Where("extra_data LIKE CONCAT('%', ?, '%')", trimmedName)
  201. }
  202. return query
  203. }
  204. // getShardingManager 获取分表管理器
  205. func (r *logRepository) getShardingManager() *sharding.ShardingManager {
  206. // 使用月度分表策略
  207. strategy := sharding.NewMonthlyShardingStrategy()
  208. return sharding.NewShardingManager(strategy, r.Logger)
  209. }