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

基于MongoDB实现草稿与发布系统的技术方案问询

Hey there! Let's work through implementing this draft and publishing system for your MongoDB setup. I’ve built similar systems before, so I’ll share practical, actionable steps tailored to your collection structure and requirements.

Your existing book and page collections are solid, but adding a few fields will make tracking draft lineage, versions, and timing much smoother:

  • For the book collection:
    • sourceBookId: ObjectId (optional, references the original book this draft was cloned from)
    • createdAt: Date (auto-set when the book is created)
    • updatedAt: Date (auto-updated on every modification)
    • version: Number (increments each time the book is published, helps with clear version tracking)

These fields don’t break your existing schema but add critical context for managing drafts and published content.

2. Core Operations Implementation

Let’s dive into the key workflows you need to support:

2.1 Creating a Draft from Any Book Status

Since you can create a draft from any book state (draft, published, archived), you have two main approaches depending on whether drafts need independent page edits:

Option 1: Full Clone (Best for Independent Drafts)

If drafts need their own editable pages (most common case), clone both the book metadata and all associated pages:

// 1. Clone the original book's metadata
const originalBook = db.books.findOne({ _id: ObjectId("original-book-id") });
const draftBook = {
  ...originalBook,
  _id: new ObjectId(),
  status: "draft",
  sourceBookId: originalBook._id,
  createdAt: new Date(),
  updatedAt: new Date(),
  version: 1 // Start fresh version for the draft
};
delete draftBook.__v; // Remove Mongoose versioning field if applicable
db.books.insertOne(draftBook);

// 2. Clone all pages linked to the original book
const originalPages = db.pages.find({ bookId: originalBook._id }).toArray();
const draftPages = originalPages.map(page => ({
  ...page,
  _id: new ObjectId(),
  bookId: draftBook._id,
  createdAt: new Date(),
  updatedAt: new Date()
}));
db.pages.insertMany(draftPages);

Option 2: Linked Pages (Lightweight, Read-Only Drafts)

If you only need to edit book metadata (name/description) and don’t need to modify pages, you can link the draft to the original book’s pages. Note: This works only if the original book’s pages won’t be deleted/archived, so use this cautiously.

2.2 Publishing a Draft Book

When ready to publish a draft, you have two strategies based on whether you want to replace the original book or keep it as a historical version:

Strategy A: Replace the Original Book (Archive History)

This is ideal if you want a single "live" version of a book:

const draftBook = db.books.findOne({ _id: ObjectId("draft-book-id"), status: "draft" });
if (!draftBook) throw new Error("Draft not found");

// Archive the original source book (if it exists)
if (draftBook.sourceBookId) {
  db.books.updateOne(
    { _id: draftBook.sourceBookId },
    { $set: { status: "archived", updatedAt: new Date() } }
  );
}

// Publish the draft
db.books.updateOne(
  { _id: draftBook._id },
  { 
    $set: { 
      status: "published", 
      updatedAt: new Date(),
      version: draftBook.version + 1 
    } 
  }
);

Strategy B: Keep Draft as a New Published Book

If you want multiple published versions of the same content, simply update the draft’s status to published without modifying the original book.

2.3 Archiving a Book

Archiving should lock the book from further edits. Add a check to ensure you only archive draft or published books:

db.books.updateOne(
  { _id: ObjectId("book-id"), status: { $in: ["draft", "published"] } },
  { $set: { status: "archived", updatedAt: new Date() } }
);
3. Query Strategies

Here are common queries to support your system:

  • Get all published books for a user:
    db.books.find({ userId: "user-id", status: "published" }).sort({ updatedAt: -1 });
    
  • Find all drafts created from a specific original book:
    db.books.find({ sourceBookId: ObjectId("original-book-id"), status: "draft" });
    
  • Fetch all pages for a published book (sorted by name):
    db.pages.find({ bookId: ObjectId("published-book-id") }).sort({ name: 1 });
    
4. Performance Tips for Large Books

Since each book can have thousands of pages, optimize for speed and scalability:

  • Add these indexes to reduce query time:
    // Books collection
    db.books.createIndex({ userId: 1, status: 1 });
    db.books.createIndex({ sourceBookId: 1, status: 1 });
    db.books.createIndex({ updatedAt: -1 });
    
    // Pages collection
    db.pages.createIndex({ bookId: 1 });
    db.pages.createIndex({ bookId: 1, name: 1 });
    
  • Use insertMany instead of multiple insertOne calls when cloning pages to minimize database round-trips.
  • For books with 10k+ pages, handle cloning asynchronously (e.g., with a background job) so users don’t wait for the entire process to finish.
5. Validation & Guardrails

Prevent invalid states and enforce rules:

  • Use MongoDB schema validation to restrict status values:
    db.createCollection("books", {
      validator: {
        $jsonSchema: {
          bsonType: "object",
          required: ["userId", "name", "status"],
          properties: {
            status: {
              bsonType: "string",
              enum: ["draft", "published", "archived"],
              description: "Status must be one of: draft, published, archived"
            }
          }
        }
      }
    });
    
  • Block edits to archived books in your application logic:
    const book = db.books.findOne({ _id: ObjectId("book-id") });
    if (book.status === "archived") {
      throw new Error("Cannot modify archived books");
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:12:18