package dao import ( "context" "fmt" "time" "dd_fiber_api/internal/question" "dd_fiber_api/pkg/database" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // MaterialDAOMongo MongoDB 实现的材料数据访问对象 type MaterialDAOMongo struct { client *database.MongoDBClient collection *mongo.Collection } // NewMaterialDAOMongo 创建 MongoDB 材料 DAO func NewMaterialDAOMongo(client *database.MongoDBClient) *MaterialDAOMongo { return &MaterialDAOMongo{ client: client, collection: client.Collection("materials"), } } // MaterialDocument MongoDB 文档结构 type MaterialDocument struct { ID string `bson:"_id" json:"id"` Type string `bson:"type" json:"type"` // 材料类型:objective 或 subjective Name string `bson:"name" json:"name"` Content string `bson:"content" json:"content"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` DeletedAt *int64 `bson:"deleted_at,omitempty" json:"deleted_at,omitempty"` } // Create 创建材料 func (dao *MaterialDAOMongo) Create(material *question.Material) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() doc := &MaterialDocument{ ID: material.ID, Type: string(material.Type), Name: material.Name, Content: material.Content, CreatedAt: material.CreatedAt, UpdatedAt: material.UpdatedAt, } _, err := dao.collection.InsertOne(ctx, doc) if err != nil { return fmt.Errorf("插入材料失败: %v", err) } return nil } // GetByID 根据ID获取材料 func (dao *MaterialDAOMongo) GetByID(id string) (*question.Material, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var doc MaterialDocument filter := bson.M{ "_id": id, "deleted_at": bson.M{"$exists": false}, } err := dao.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, fmt.Errorf("材料不存在") } return nil, fmt.Errorf("查询材料失败: %v", err) } return dao.documentToMaterial(&doc), nil } // Search 搜索材料 func (dao *MaterialDAOMongo) Search(query string, materialType question.MaterialType, page, pageSize int32) ([]*question.Material, int32, error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() filter := bson.M{ "deleted_at": bson.M{"$exists": false}, } // 按类型过滤 if materialType != "" { filter["type"] = string(materialType) } // 全文搜索(支持名称和内容) if query != "" { filter["$or"] = []bson.M{ {"name": bson.M{"$regex": query, "$options": "i"}}, {"content": bson.M{"$regex": query, "$options": "i"}}, } } // 查询总数 total, err := dao.collection.CountDocuments(ctx, filter) if err != nil { return nil, 0, fmt.Errorf("查询总数失败: %v", err) } // 查询数据 skip := int64((page - 1) * pageSize) limit := int64(pageSize) opts := options.Find(). SetSkip(skip). SetLimit(limit). SetSort(bson.M{"created_at": -1}) cursor, err := dao.collection.Find(ctx, filter, opts) if err != nil { return nil, 0, fmt.Errorf("查询材料失败: %v", err) } defer cursor.Close(ctx) var docs []MaterialDocument if err := cursor.All(ctx, &docs); err != nil { return nil, 0, fmt.Errorf("解析材料数据失败: %v", err) } materials := make([]*question.Material, len(docs)) for i, doc := range docs { materials[i] = dao.documentToMaterial(&doc) } return materials, int32(total), nil } // Update 更新材料 func (dao *MaterialDAOMongo) Update(material *question.Material) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() filter := bson.M{ "_id": material.ID, "deleted_at": bson.M{"$exists": false}, } update := bson.M{ "$set": bson.M{ "type": string(material.Type), "name": material.Name, "content": material.Content, "updated_at": material.UpdatedAt, }, } result, err := dao.collection.UpdateOne(ctx, filter, update) if err != nil { return fmt.Errorf("更新材料失败: %v", err) } if result.MatchedCount == 0 { return fmt.Errorf("材料不存在或已被删除") } return nil } // Delete 删除材料(软删除) func (dao *MaterialDAOMongo) Delete(id string) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() filter := bson.M{ "_id": id, "deleted_at": bson.M{"$exists": false}, } now := time.Now().Unix() update := bson.M{ "$set": bson.M{ "deleted_at": now, "updated_at": now, }, } result, err := dao.collection.UpdateOne(ctx, filter, update) if err != nil { return fmt.Errorf("删除材料失败: %v", err) } if result.MatchedCount == 0 { return fmt.Errorf("材料不存在或已被删除") } return nil } // documentToMaterial 将 MongoDB 文档转换为 Material 对象 func (dao *MaterialDAOMongo) documentToMaterial(doc *MaterialDocument) *question.Material { materialType := question.MaterialType(doc.Type) // 兼容旧数据:如果没有 type 字段,默认为 objective if materialType == "" { materialType = question.MaterialTypeObjective } return &question.Material{ ID: doc.ID, Type: materialType, Name: doc.Name, Content: doc.Content, CreatedAt: doc.CreatedAt, UpdatedAt: doc.UpdatedAt, } } // CreateIndexes 创建索引 func (dao *MaterialDAOMongo) CreateIndexes() error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() indexes := []mongo.IndexModel{ { Keys: bson.D{ {Key: "name", Value: 1}, }, }, { Keys: bson.D{ {Key: "created_at", Value: -1}, }, }, { Keys: bson.D{ {Key: "name", Value: "text"}, {Key: "content", Value: "text"}, }, Options: options.Index().SetName("text_index"), }, } _, err := dao.collection.Indexes().CreateMany(ctx, indexes) if err != nil { return fmt.Errorf("创建索引失败: %v", err) } return nil }