Kommentointijärjestelmän osien lisääminen 1 - Tietokannan perustaminen (Suomi (Finnish))

Kommentointijärjestelmän osien lisääminen 1 - Tietokannan perustaminen

Monday, 26 August 2024

//

5 minute read

Esitys

Yksi blogging-sivuston keskeisistä näkökohdista on kommenttijärjestelmä. Näin ollen, päätin rakentaa yhdenM SK2 Tämä on ensimmäinen osa sarjaa posteja siitä, miten rakentaa kommenttijärjestelmää . Tässä postissa

Tietokannan perustaminen

Kuten muutkin sivustot, minä käytän Postgres-järjestelmää blogeihin.

EU:n toimintaympäristö

Nykyisessä järjestelmässä me käytämme 'Koment Closure' määritelläksemme kommenttien väliset suhteet -, jotta voin tukea threaded-kommentioitaM SK3 Oman luokan taulukkomme on tämä

classDiagram
    class CommentEntity {
        int Id
        CommentStatus Status
        string Author
        string? HtmlContent
        string Content
        DateTime CreatedAt
        int PostId
        int? ParentCommentId
    }

    class BlogPostEntity {
        int Id
        string Title
        string Content
        DateTime CreatedAt
    }

    class CommentClosure {
        int AncestorId
        int DescendantId
        int Depth
    }

    CommentEntity "1" --> "1" BlogPostEntity : belongs to
    CommentEntity "1" --> "0..1" CommentEntity : parent
    CommentEntity "0..*" --> "0..*" CommentClosure : Ancestors
    CommentEntity "0..*" --> "0..*" CommentClosure : Descendants
    CommentClosure "1" --> "1" CommentEntity : Ancestor
    CommentClosure "1" --> "1" CommentEntity : Descendant

    class CommentStatus {
        <<enum>>
        Pending
        Approved
        Rejected
        Deleted
    }

Voitte nähdä, että jokainen CommentEntity on PostId postiin liitetään ' ja , ParentCommentId huomautuksena on, että ' liitetään ( jos jokin CommentClosure taulukkoa käytetään kommenttien välisten suhteiden säilyttämiseen.

Kommenttipalvelu

Euroopan unionin CommentService on vastuussa huomautusten hallinnoinnista. Siihen sisältyy menetelmiä huomautuksen lisäämiseen, hyväksymiseen, hylkäämiseen, poistamiseen ja poistamiseen.

Tämän palvelun käyttöliittymämme näyttää seuraavalta:

public interface ICommentService
{
    Task<string> Add( int postId, int? parentCommentId, string author, string content);
    Task<List<CommentEntity>> GetForPost(int blogPostId, int page = 1, int pageSize = 10, int? maxDepth = null, CommentStatus? status = null);
    Task<List<CommentEntity>> GetDescendants(int commentId, int maxDepth = 0);

     Task<CommentEntity> Get(int commentId);
    Task<List<CommentEntity>> GetAncestors(int commentId);
    Task Delete(int commentId);
    Task Reject(int commentId);
    Task Approve(int commentId);
}

Lisämenetelmä

Lisämenetelmämme on tämän kommenttijärjestelmän rakenteen yhteenveto.

Tämä on tärkeää, kun on useita toimintoja, jotka on tehtävä yhdessä.

Sen jälkeen se analysoi huomautuksen tekstiä Markdownista HTML:hen Markdigin kirjaston avulla. Tämä on yksinkertainen tapa antaa käyttäjille mahdollisuus muotoilla huomautustensa Markdownin avulla .

Seuraavaksi se luo yksikkön ja tallentaa sen tietokantaan.

Tämä tehdään kahdessa vaiheessa: - ensiksi kommentti on tallennettu, sitten kommentin sulkeminen on talletettuM SK2 Tämä johtuu siitä, että kommenttin on tallennettava, jotta voidaan tuottaa Id ,, jota käytetään sulkemisessa

Seuraavaksi rakennamme hierarkian CommentClosures , jotka määrittelevät kommenttien väliset suhteet. Aloitamme itsestä-johdanto-osan sulkemista koskevasta entryistäM SK2 ja sitten jos on vanhempien kommentti, otamme kaikki vanhemman kommentin edeltäjät ja lisäämme ne uuteen kommenttiin.

Lopuksi totean, että teemme sopimuksen.

 public async Task<string> Add(int postId, int? parentCommentId, string author, string content)
  {
      await using var transaction = await context.Database.BeginTransactionAsync();
      try
      {
         var html = Markdig.Markdown.ToHtml(content);
          // Create the new comment
          var newComment = new CommentEntity()
          {
              HtmlContent = html,
              Content = content,
              CreatedAt = DateTime.UtcNow,
              PostId = postId,
              Author = author,
              Status = CommentStatus.Pending,
              ParentCommentId = parentCommentId
          };
            
          context.Comments.Add(newComment);
          await context.SaveChangesAsync();
          logger.LogInformation("Saved comment to DB");// Save to generate the new comment's Id

          // Insert into CommentClosure table
          var commentClosures = new List<CommentClosure>();

          // Self-referencing closure entry
          commentClosures.Add(new CommentClosure
          {
              AncestorId = newComment.Id,
              DescendantId = newComment.Id,
              Depth = 0
          });

          // If there is a parent comment, insert the ancestor relationships
          if (parentCommentId.HasValue)
          {
              // Fetch all ancestors of the parent comment
              var parentAncestors = await context.CommentClosures
                  .Where(cc => cc.DescendantId == parentCommentId.Value)
                  .ToListAsync();

              // Add ancestor relationships for the new comment
              foreach (var ancestor in parentAncestors)
              {
                  commentClosures.Add(new CommentClosure
                  {
                      AncestorId = ancestor.AncestorId,
                      DescendantId = newComment.Id,
                      Depth = ancestor.Depth + 1
                  });
              }

              // Add a direct parent-child relationship
              commentClosures.Add(new CommentClosure
              {
                  AncestorId = parentCommentId.Value,
                  DescendantId = newComment.Id,
                  Depth = 1
              });
          }

          context.CommentClosures.AddRange(commentClosures);
          await context.SaveChangesAsync();
          logger.LogInformation("Saved comment closure to DB");

          // Commit transaction
          await transaction.CommitAsync();
          return html;
      }
      catch (Exception e)
      {
          // Rollback transaction in case of failure
          await transaction.RollbackAsync();
          logger.LogError(e, "Failed to save comment to DB");
      }

      return string.Empty;
  }

GetForPost-menetelmä

Emme voi kattaa koko asiaa, mutta Add ja Get ovat tämän palvelun tärkeimmät CRUD-operaatiot

Kuten voitte nähdä, meillä on puheluja, jotka perustuvat korkeimman tason kommenttiin. Meillä on myös maxDepth parametri, jonka avulla voimme rajoittaa kommenttipuun syvyyttä. Tämä on hyödyllistä vain, jos haluamme vain esittää korkeimman tasonM SK1kommenttit, tai jos haluame rajoittaa puun syvyyttä suorituskyvyn parantamiseksi

Tämän recursiivisen lähestymistavan avulla meidän on turhaan sovellettava syvennysfilteriä sen jälkeen, kun olemme ottaneet huomautukset esille.

  public async Task<List<CommentEntity>> GetForPost(int blogPostId, int page = 1, int pageSize = 10, int? maxDepth = null, CommentStatus? status = null)
  {
      // Step 1: Query the top-level comments for the specified blog post
      var query = context.Comments
          .Where(c => c.PostId == blogPostId)
          .OrderByDescending(c => c.CreatedAt)
          .Skip((page - 1) * pageSize)
          .Take(pageSize);

      // Step 2: Filter by status if provided
      if (status.HasValue)
      {
          query = query.Where(c => c.Status == status.Value);
      }

      var topLevelComments = await query
          .Include(c => c.ParentComment)
          .Include(d=>d.Descendants)
          .ToListAsync();

      // Step 4: Filter descendants based on the maxDepth
      foreach (var comment in topLevelComments)
      {
          if (maxDepth != null)
          {
              FilterDescendantsByDepth(comment, 0, maxDepth.Value);
          }
      }

      return topLevelComments;
  }

// Recursive helper method to limit the descendants based on the specified depth
  private void FilterDescendantsByDepth(CommentEntity comment, int currentDepth, int maxDepth)
  {
      if (currentDepth >= maxDepth)
      {
          // If the max depth is reached or there are no descendants, stop recursion
          comment.Descendants = new List<CommentClosure>();  // Clear further descendants beyond maxDepth
          return;
      }

      foreach (var closure in comment.Descendants.ToList())  // Iterate over a copy to prevent modification during iteration
      {
          FilterDescendantsByDepth(closure.Descendant, currentDepth + 1, maxDepth);
      }
  }

Päätelmissä

Tämä on yksinkertainen threaded kommenttijärjestelmä, jossa käytetään rekursiivisia kysymyksiä, jotta voidaan tallentaa kommenttien välisiä suhteita.. Se ei ole tehokkain järjestelmä, mutta se on yksinomainen ja se toimii.

Tähän asti, jättäkää vapaasti huomautuksensa jäljempänäM SK1

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.