agentSessionRepo.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package mongo
  2. import (
  3. "context"
  4. "errors"
  5. "time"
  6. "fwh-toolbox-server/database"
  7. "go.mongodb.org/mongo-driver/bson"
  8. "go.mongodb.org/mongo-driver/mongo"
  9. "go.mongodb.org/mongo-driver/mongo/options"
  10. )
  11. // 新 AI 助手(AgentServer)的会话归属与子资源索引,见 docs/ai-assistant-agentserver/technical-design.md §3.3。
  12. const (
  13. CollectionName_AgentSessions = "AgentSessions"
  14. CollectionName_AgentResources = "AgentResources"
  15. )
  16. // AgentResourceTTL 子资源索引保留期(与会话保留期一致)
  17. const AgentResourceTTL = 180 * 24 * time.Hour
  18. // AgentSession 会话归属记录
  19. type AgentSession struct {
  20. SessionId string `bson:"sessionId"`
  21. UserId string `bson:"userId"`
  22. ProfileId string `bson:"profileId"`
  23. Title string `bson:"title"`
  24. CreatedAt time.Time `bson:"createdAt"`
  25. LastActive time.Time `bson:"lastActive"`
  26. DeletedAt *time.Time `bson:"deletedAt,omitempty"`
  27. }
  28. // AgentResource 子资源(run / 交互 / 文件)所属会话
  29. type AgentResource struct {
  30. ResourceId string `bson:"resourceId"`
  31. SessionId string `bson:"sessionId"`
  32. CreatedAt time.Time `bson:"createdAt"`
  33. }
  34. // InitAgentIndexes 创建索引:会话 ID 唯一、按用户列表、子资源 ID 唯一、子资源 TTL
  35. func InitAgentIndexes(ctx context.Context) error {
  36. if _, err := database.GetCollection(CollectionName_AgentSessions).Indexes().CreateMany(ctx, []mongo.IndexModel{
  37. {Keys: bson.D{{Key: "sessionId", Value: 1}}, Options: options.Index().SetUnique(true)},
  38. {Keys: bson.D{{Key: "userId", Value: 1}, {Key: "deletedAt", Value: 1}, {Key: "lastActive", Value: -1}}},
  39. }); err != nil {
  40. return err
  41. }
  42. _, err := database.GetCollection(CollectionName_AgentResources).Indexes().CreateMany(ctx, []mongo.IndexModel{
  43. {Keys: bson.D{{Key: "resourceId", Value: 1}}, Options: options.Index().SetUnique(true)},
  44. {Keys: bson.D{{Key: "createdAt", Value: 1}}, Options: options.Index().SetExpireAfterSeconds(int32(AgentResourceTTL / time.Second))},
  45. })
  46. return err
  47. }
  48. // InsertAgentSession 写入新会话
  49. func InsertAgentSession(ctx context.Context, s AgentSession) error {
  50. _, err := database.GetCollection(CollectionName_AgentSessions).InsertOne(ctx, s)
  51. return err
  52. }
  53. // GetAgentSession 查询会话(含已删除);不存在返回 nil
  54. func GetAgentSession(ctx context.Context, sessionId string) (*AgentSession, error) {
  55. var s AgentSession
  56. err := database.GetCollection(CollectionName_AgentSessions).FindOne(ctx, bson.M{"sessionId": sessionId}).Decode(&s)
  57. if errors.Is(err, mongo.ErrNoDocuments) {
  58. return nil, nil
  59. }
  60. if err != nil {
  61. return nil, err
  62. }
  63. return &s, nil
  64. }
  65. // ListAgentSessionsByUser 用户未删除的会话,按 lastActive 倒序
  66. func ListAgentSessionsByUser(ctx context.Context, userId string, limit int64) ([]AgentSession, error) {
  67. cur, err := database.GetCollection(CollectionName_AgentSessions).Find(ctx,
  68. bson.M{"userId": userId, "deletedAt": bson.M{"$exists": false}},
  69. options.Find().SetSort(bson.D{{Key: "lastActive", Value: -1}}).SetLimit(limit))
  70. if err != nil {
  71. return nil, err
  72. }
  73. defer cur.Close(ctx)
  74. list := make([]AgentSession, 0)
  75. if err := cur.All(ctx, &list); err != nil {
  76. return nil, err
  77. }
  78. return list, nil
  79. }
  80. // UpdateAgentSessionTitle 修改标题(只改本人的未删除会话)
  81. func UpdateAgentSessionTitle(ctx context.Context, userId, sessionId, title string) error {
  82. _, err := database.GetCollection(CollectionName_AgentSessions).UpdateOne(ctx,
  83. bson.M{"userId": userId, "sessionId": sessionId, "deletedAt": bson.M{"$exists": false}},
  84. bson.M{"$set": bson.M{"title": title}})
  85. return err
  86. }
  87. // TouchAgentSession 刷新 lastActive
  88. func TouchAgentSession(ctx context.Context, userId, sessionId string) error {
  89. _, err := database.GetCollection(CollectionName_AgentSessions).UpdateOne(ctx,
  90. bson.M{"userId": userId, "sessionId": sessionId, "deletedAt": bson.M{"$exists": false}},
  91. bson.M{"$set": bson.M{"lastActive": time.Now()}})
  92. return err
  93. }
  94. // SoftDeleteAgentSession 软删除
  95. func SoftDeleteAgentSession(ctx context.Context, userId, sessionId string) error {
  96. _, err := database.GetCollection(CollectionName_AgentSessions).UpdateOne(ctx,
  97. bson.M{"userId": userId, "sessionId": sessionId, "deletedAt": bson.M{"$exists": false}},
  98. bson.M{"$set": bson.M{"deletedAt": time.Now()}})
  99. return err
  100. }
  101. // GetAgentResourceSession 子资源所属会话;不存在返回空串
  102. func GetAgentResourceSession(ctx context.Context, resourceId string) (string, error) {
  103. var r AgentResource
  104. err := database.GetCollection(CollectionName_AgentResources).FindOne(ctx, bson.M{"resourceId": resourceId}).Decode(&r)
  105. if errors.Is(err, mongo.ErrNoDocuments) {
  106. return "", nil
  107. }
  108. if err != nil {
  109. return "", err
  110. }
  111. return r.SessionId, nil
  112. }
  113. // UpsertAgentResources 批量写入子资源索引;已存在的不覆盖所属会话
  114. func UpsertAgentResources(ctx context.Context, rs []AgentResource) error {
  115. if len(rs) == 0 {
  116. return nil
  117. }
  118. models := make([]mongo.WriteModel, 0, len(rs))
  119. for _, r := range rs {
  120. models = append(models, mongo.NewUpdateOneModel().
  121. SetFilter(bson.M{"resourceId": r.ResourceId}).
  122. SetUpdate(bson.M{"$setOnInsert": r}).
  123. SetUpsert(true))
  124. }
  125. _, err := database.GetCollection(CollectionName_AgentResources).BulkWrite(ctx, models, options.BulkWrite().SetOrdered(false))
  126. return err
  127. }