+1

(EzyPlatform) hướng dẫn lấy ra danh sách bài viết highlight trong dự án Web Blog

Tạo ra các data trong cơ sở dữ liệu

B1: Tạo ra các term có Term Type là HIGHLIGHT. Term Type sẽ đại diện cho 1 danh mục cha, còn Term sẽ là danh mục con.

B2: Sau khi tạo ra các Term, chúng ta tiếp tục tạo ra các bài viết có những Term vừa tạo ra như sau:

Làm việc phía Backend

B1: Trong package repository, sửa nội dung interface WebBlogTermRepository như sau:

package com.blog.blog.web.repository;

import com.tvd12.ezydata.database.EzyDatabaseRepository;
import com.tvd12.ezyfox.database.annotation.EzyQuery;
import com.tvd12.ezyfox.database.annotation.EzyRepository;
import org.youngmonkeys.ezyarticle.sdk.entity.PostTerm;
import org.youngmonkeys.ezyarticle.sdk.entity.PostTermId;
import org.youngmonkeys.ezyarticle.sdk.entity.Term;

import java.util.Collection;
import java.util.List;


@EzyRepository
public interface WebBlogTermRepository
        extends EzyDatabaseRepository<PostTermId, PostTerm> {

    @EzyQuery(
        "SELECT e FROM PostTerm e " +
            "INNER JOIN Term t " +
            "ON e.termId = t.id " +
            "WHERE t.name IN ?0"
    )
    List<PostTerm> findHighlightPostTermsByTermNameIn(
        Collection<String> termNames
    );
}

Đây là hàm để truy vấn các thông tin của các PostTerm theo một danh sách tên term.

B2: Tạo ra 1 package có tên là model chứa 1 class WebPostTermModel có nội dung như sau:

package com.blog.blog.web.model;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class WebPostTermModel {
    private final long postId;
    private final long termId;
}

B3: Trong package converter, tạo ra class WebBlogEntityToModelConverter có nội dung như sau:

package com.blog.blog.web.converter;

import com.blog.blog.web.model.WebPostTermModel;
import com.tvd12.ezyfox.bean.annotation.EzySingleton;
import org.youngmonkeys.ezyarticle.sdk.entity.PostTerm;

@EzySingleton
public class WebBlogEntityToModelConverter {

    public WebPostTermModel toModel(PostTerm entity) {
        if (entity == null) {
            return null;
        }
        return WebPostTermModel.builder()
            .postId(entity.getPostId())
            .termId(entity.getTermId())
            .build();
    }
}

B4: Sửa nội dung của class WebBlogTermService trong package service như sau:

package com.blog.blog.web.service;

import com.blog.blog.web.converter.WebBlogEntityToModelConverter;
import com.blog.blog.web.model.WebPostTermModel;
import com.blog.blog.web.repository.WebBlogTermRepository;
import com.tvd12.ezyhttp.server.core.annotation.Service;
import lombok.AllArgsConstructor;

import java.util.Collection;
import java.util.List;

import static com.tvd12.ezyfox.io.EzyLists.newArrayList;

@Service
@AllArgsConstructor
public class WebBlogTermService {

    private final WebBlogTermRepository blogTermRepository;
    private final WebBlogEntityToModelConverter entityToModelConverter;

    public List<WebPostTermModel> getHighlightPostTermsByTermNames(
        Collection<String> termNames
    ) {
        return newArrayList(
            blogTermRepository.findHighlightPostTermsByTermNameIn(
                termNames
            ),
            entityToModelConverter::toModel
        );
    }
}

Tại đây viết hàm getHighlightPostTermsByTermNames có tham số truyền vào là 1 danh sách termNames. Hàm này có nhiệm vụ là chuyển đổi từ danh sách PostTerm mình lấy được trong hàm findHighlightPostTermsByTermNameIn() sang 1 List các WebPostTermModel qua hàm toModel trong class WebBlogEntityToModelConverter .

B5: Trong WebBlogPostModelDecorator, viết 2 hàm decoratePostTerms() như sau:

package com.blog.blog.web.controller.decorator;

import com.blog.blog.web.converter.WebBlogModelToResponseConverter;
import com.blog.blog.web.model.WebPostTermModel;
import com.blog.blog.web.response.WebBlogPostResponse;
import com.blog.blog.web.service.WebBlogTermService;
import com.tvd12.ezyfox.bean.annotation.EzySingleton;
import lombok.AllArgsConstructor;
import org.youngmonkeys.ezyarticle.sdk.model.PostModel;
import org.youngmonkeys.ezyarticle.sdk.model.TermModel;
import org.youngmonkeys.ezyarticle.web.service.WebPostService;
import org.youngmonkeys.ezyarticle.web.service.WebPostSlugService;
import org.youngmonkeys.ezyarticle.web.service.WebTermService;
import org.youngmonkeys.ezyplatform.model.MediaNameModel;
import org.youngmonkeys.ezyplatform.model.UuidNameModel;
import org.youngmonkeys.ezyplatform.web.service.WebAdminService;
import org.youngmonkeys.ezyplatform.web.service.WebMediaService;

import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.tvd12.ezyfox.io.EzyLists.newArrayList;
import static com.tvd12.ezyfox.io.EzyMaps.newHashMap;
import static com.tvd12.ezyfox.io.EzySets.newHashSet;

@EzySingleton
@AllArgsConstructor
public class WebBlogPostModelDecorator {

    private final WebAdminService adminService;
    private final WebBlogTermService blogTermService;
    private final WebMediaService mediaService;
    private final WebPostService postService;
    private final WebPostSlugService postSlugService;
    private final WebTermService termService;
    private final WebBlogModelToResponseConverter blogModelToResponseConverter;

    public List<WebBlogPostResponse> decoratePostTerms(
        List<WebPostTermModel> models
    ) {
        Set<Long> postIds = newHashSet(
            models,
            WebPostTermModel::getPostId
        );
        Map<Long, PostModel> postById = postService.getPostMapByIds(
            postIds
        );
        return decoratePostTerms(
            models,
            postById
        );
    }

    public List<WebBlogPostResponse> decoratePostTerms(
        List<WebPostTermModel> models,
        Map<Long, PostModel> postById
    ) {
        Set<Long> postIds = newHashSet(
            models,
            WebPostTermModel::getPostId
        );
        Set<Long> imageIds = newHashSet(
            postById.values(),
            PostModel::getFeaturedImageId
        );
        Map<Long, MediaNameModel> imageById = mediaService
            .getMediaNameMapByIds(imageIds);

        Set<Long> authorIds = newHashSet(
            postById.values(),
            PostModel::getAuthorAdminId
        );
        Map<Long, UuidNameModel> authorById = adminService
            .getAdminUuidNameMapByIds(authorIds);

        Map<Long, String> slugByPostId = postSlugService.getLatestSlugMapByPostIds(
            postIds
        );

        Set<Long> termIds = newHashSet(
            models,
            WebPostTermModel::getTermId
        );
        Map<Long, TermModel> termById = termService.getTermMapByIds(
            termIds
        );
        return newArrayList(
            models,
            postTerm -> {
                PostModel post = postById.get(postTerm.getPostId());
                return blogModelToResponseConverter.toPostResponse(
                    post,
                    authorById.get(post.getAuthorAdminId()),
                    imageById.getOrDefault(
                        post.getFeaturedImageId(),
                        MediaNameModel.builder().build()
                    ),
                    termById.get(postTerm.getTermId()),
                    slugByPostId.get(post.getId())
                );
            }
        );
    }
}
  1. Hàm thứ nhất: có tham số truyền vào là 1 List<WebPostTermModel>, chúng ta lấy được 1 set các PostId, sau đó tạo ra 1 map các PostModel có key là PostId qua phương thức postService.getPostMapByIds(postTds) có sẵn trong thư viện. Cuối cung là return lại hàm decoratePostTerms thứ 2.

  2. Hàm thứ hai: có 2 tham số truyền vào là List<WebPostTermModel>Map<Long,PostModel> có key là PostId. Sau khi lấy được các dữ liệu cần thiết ta sử dụng hàm blogModelToResponseConverter.toPostResponse() để trả về dữ liệu cần thiết là 1 List<WebBlogPostResponse>.

    • Từ List<WebPostTermModel>, ta lấy ra được postIdstermIds. Sau đó, từ postIds, ta lấy được slugByPostId - map các slug có key là postId. Còn termIds lấy được termById - map các TermModel có key là termId.
    • Từ Map<Long,PostModel>, ta lấy được imageIdsauthorIds. Từ imageIds lấy được map các hình ảnh và từ authorIds sẽ lấy được map các tác giả.

B6: Trong WebBlogPostControllerService, viết hàm getHighlightPosts() trả về List các WebBlogPostResponse. Hàm này sử dụng getHighlightPostTermsByTermNames() để lấy ra list WebPostTermModel. Sau đó lấy list này làm tham số cho hàm decoratePostTerms() để lấy được danh sách WebBlogPostResponse cần lấy.

package com.blog.blog.web.controller.service;

import com.blog.blog.web.controller.decorator.WebBlogPostModelDecorator;
import com.blog.blog.web.model.WebPostTermModel;
import com.blog.blog.web.response.WebBlogPostResponse;
import com.blog.blog.web.service.WebBlogPostService;
import com.blog.blog.web.service.WebBlogTermService;
import com.tvd12.ezyhttp.server.core.annotation.Service;
import lombok.AllArgsConstructor;
import org.youngmonkeys.ezyarticle.sdk.model.PostModel;

import java.util.Arrays;
import java.util.List;


@Service
@AllArgsConstructor
public class WebBlogPostControllerService {

    private final WebBlogTermService blogTermService;
    private final WebBlogPostService webBlogPostService;
    private final WebBlogPostModelDecorator blogPostModelDecorator;

    public List<WebBlogPostResponse> getHighlightPosts() {
        List<WebPostTermModel> postTerms = blogTermService
            .getHighlightPostTermsByTermNames(
                Arrays.asList(
                    "main1",
                    "main2",
                    "main3",
                    "main4"
                )
            );
        return blogPostModelDecorator.decoratePostTerms(
            postTerms
        );
    }
}

B7:: Cuối cùng, tại HomeController sửa như sau:

package com.blog.blog.web.controller.view;

import com.blog.blog.web.controller.service.WebBlogPostControllerService;
import com.blog.blog.web.response.WebBlogPostResponse;
import com.tvd12.ezyhttp.server.core.annotation.Controller;
import com.tvd12.ezyhttp.server.core.annotation.DoGet;
import com.tvd12.ezyhttp.server.core.view.View;
import lombok.AllArgsConstructor;

import java.util.List;

import static org.youngmonkeys.ezyplatform.constant.CommonConstants.VIEW_VARIABLE_PAGE_TITLE;

@Controller
@AllArgsConstructor
public class HomeController {
    private final WebBlogPostControllerService blogPostControllerService;


    @DoGet("/")
    public View home() {
        return View.builder()
            .template("home") //--> Hàm
            .addVariable(
                "highlightPosts",
                blogPostControllerService.getHighlightPosts()
            )
            .addVariable(VIEW_VARIABLE_PAGE_TITLE, "Trang chủ")
            .build();
    }
}

Làm việc phía Fontend

Sửa lại html phần blog highlight như sau:

 <div class="row mt-4">
            <div th:if="${highlightPosts.size() > 0}" class="col-8">
                <div class="card overflow-hidden " >
                    <div class="card-img position-relative">
                        <img loading="lazy" decoding="async" th:src="${highlightPosts.get(0).image.getUrlOrNull()}"  alt="img1">
                        <div class="position-absolute bottom-0 start-50 translate-middle-x card-info w-100 text-center">
                            <button class="tag text-capitalize" >[[${highlightPosts.get(0).term.name}]]</button><br>
                            <h3 class="line-container text-capitalize">[[${highlightPosts.get(0).title}]]</h3>
                            <p class="text-capitalize date-string">[[${highlightPosts.get(0).publishedAt}]]</p>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-4">
                <div class="card overflow-hidden" style="height: 576px">
                    <div class="card-img position-relative">
                        <img loading="lazy" decoding="async" th:src="${highlightPosts.get(1).image.getUrlOrNull()}" alt="img2">
                        <div class="position-absolute bottom-0 start-50 translate-middle-x card-info w-100 text-center">
                            <button class="tag text-capitalize" >[[${highlightPosts.get(1).term.name}]]</button><br>
                            <h3 class="line-container text-capitalize">[[${highlightPosts.get(1).title}]]</h3>
                            <p class="text-capitalize date-string">[[${highlightPosts.get(1).publishedAt}]]</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>

Sau khi sửa xong ta sẽ có được giao diện bao gồm ảnh, term, title, publishedAt như sau:


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.