You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言MongoDB DELETE路由FindOne返回mongo.ErrNoDocuments故障排查

问题:Go MongoDB HTTP服务器DELETE路由无法找到存在的文档

我用Go编写了一个与MongoDB交互的HTTP服务器,GET、POST、PUT路由均正常工作(其中PUT路由逻辑与DELETE最相似且运行正常),但DELETE路由存在异常:明明UUID对应的文档确实存在,且其他路由使用该UUID能正常操作,DELETE路由执行FindOne时却返回mongo.ErrNoDocuments错误。刚接触Go,肯定遗漏了某些细节,希望得到帮助。


DELETE路由代码

/* DELETE /{uuid}
r.Body:
    "accessKey"  -> required

Deletes an existing Paste in the MongoDB database
*/
func (s *Server) deletePaste() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        defer func() {
            log.Printf("%s %s [%v]",
                r.Method,
                r.URL.Path,
                time.Since(start),
            )
        }()

        uuidStr, _ := mux.Vars(r)["uuid"]
        fmt.Println(uuidStr)

        body := struct {
            AccessKey string `json:"accessKey,omitempty"`
        }{}
        if err := decodeJSONBody(w, r, &body); err != nil {
            var mr *badRequest
            if errors.As(err, &mr) {
                http.Error(w, mr.msg, mr.status)
            } else {
                log.Print(err.Error())
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
            return
        }

        coll := s.Client.Database("paste").Collection("files")
        // Check document exists and accessKey is the same
        var result bson.M
        filter := bson.M{"uuid": uuidStr}
        project := bson.M{
            "_id":       0,
            "accessKey": 1,
        }

        err := coll.FindOne(
            context.TODO(),
            filter,
            options.FindOne().SetProjection(project),
        ).Decode(&result)

        if err != nil {
            if err == mongo.ErrNoDocuments {
                http.Error(w, "No document found with that UUID", http.StatusBadRequest)
                return
            }
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // Check the sender can actually edit the paste
        if body.AccessKey != result["accessKey"] {
            http.Error(w, "Invalid access key", http.StatusUnauthorized)
            return
        }

        // Delete matching document
        //opts := options.Delete().SetHint(bson.D{{Key: "uuid", Value: 1}})
        res, err := coll.DeleteOne(context.TODO(), filter, nil)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        if res.DeletedCount == 0 {
            http.Error(w, "Error matching and deleting document", http.StatusInternalServerError)
            return
        }

        response := make(map[string]string)
        response["info"] = "Document deleted"

        w.Header().Set("Content-Type", "application/json")
        if err := json.NewEncoder(w).Encode(response); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
}

PUT路由代码

/* PUT /{uuid}
r.Body:
    "accessKey"  -> required
    "content"     -> optional
    "name"        -> optional
    "filetype"    -> optional
    "expiresIn"   -> optional
    ^ At least one of the 4 optional fields must be updated

Updates an existing Paste in the MongoDB database and returns a JSON document
{
    uuid:       UUID,
    name:       String,
    content:    String,
    filetype:   String,
    accessKey:  String,
    expiresAt:  Date
}
*/
func (s *Server) updatePaste() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        defer func() {
            log.Printf("%s %s [%v]",
                r.Method,
                r.URL.Path,
                time.Since(start),
            )
        }()

        uuidStr, _ := mux.Vars(r)["uuid"]

        var paste Paste
        var body PasteBody
        if err := decodeJSONBody(w, r, &body); err != nil {
            var mr *badRequest
            if errors.As(err, &mr) {
                http.Error(w, mr.msg, mr.status)
            } else {
                log.Print(err.Error())
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
            return
        }

        if err := paste.EditPaste(&body); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        doc, err := toBsonDoc(&paste)
        if err != nil {
            log.Println(err)
            http.Error(w, "Error converting request body to BSON document", http.StatusInternalServerError)
        }

        coll := s.Client.Database("pastes").Collection("files")
        // Check document and provided accessKey match
        var result bson.M
        filter := bson.M{"uuid": uuidStr}
        project := bson.M{
            "_id":       0,
            "accessKey": 1,
        }

        err = coll.FindOne(
            context.TODO(),
            filter,
            options.FindOne().SetProjection(project),
        ).Decode(&result)

        if err != nil {
            if err == mongo.ErrNoDocuments {
                http.Error(w, "No document found with that UUID", http.StatusBadRequest)
                return
            }
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // Check the sender can actually edit the paste
        if paste.AccessKey != result["accessKey"] {
            http.Error(w, "Invalid access key", http.StatusUnauthorized)
            return
        }

        // Update document
        filter = bson.M{"uuid": uuidStr}
        update := bson.M{"$set": doc}
        res, err := coll.UpdateOne(context.TODO(), filter, update)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        if res.MatchedCount == 0 || res.ModifiedCount == 0 {
            http.Error(w, "Error matching and updating document", http.StatusInternalServerError)
            return
        }

        response := make(map[string]string)
        response["uuid"] = uuidStr
        response["expiresAt"] = paste.ExpiresAt.Time().String()

        w.Header().Set("Content-Type", "application/json")
        if err := json.NewEncoder(w).Encode(response); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
}

GET路由代码

/* GET /{uuid}

Returns the Paste from the MongoDB database with the matching UUID in JSON
{
    uuid:       UUID,
    name:       String,
    content:    String,
    filetype:   String,
    accessKey:  String,
    expires:    Date
}
*/
func (s *Server) getPaste() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        defer func() {
            log.Printf("%s %s [%v]",
                r.Method,
                r.URL.Path,
                time.Since(start),
            )
        }()

        uuidStr, _ := mux.Vars(r)["uuid"]

        coll := s.Client.Database("pastes").Collection("files")
        var result bson.M
        filter := bson.M{"uuid": uuidStr}
        project := bson.M{
            "_id":       0,
            "accessKey": 0,
            "uuid":      0,
        }

        err := coll.FindOne(
            context.TODO(),
            filter,
            options.FindOne().SetProjection(project),
        ).Decode(&result)

        if err != nil {
            if err == mongo.ErrNoDocuments {
                http.Error(w, "No document found with that UUID", http.StatusBadRequest)
                return
            }
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // Convert to long date format
        result["expiresAt"] = primitive.DateTime(result["expiresAt"].(primitive.DateTime)).Time().String()

        w.Header().Set("Content-Type", "application/json")
        if err := json.NewEncoder(w).Encode(result); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
}

问题解决

排查后发现是拼写错误:DELETE路由中获取集合时使用的数据库名是"paste",而GET、PUT路由使用的正确数据库名是"pastes"。改用全局变量存储数据库名字符串后,问题得到解决。

内容的提问来源于stack exchange,提问作者h5law

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 00:49:02