agentSessionRepo.go 5.3 KB

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