248 lines
6.0 KiB
Go
248 lines
6.0 KiB
Go
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
|
||
}
|
||
|