99import org .slf4j .Logger ;
1010
1111import java .math .BigDecimal ;
12+ import java .text .Normalizer ;
1213import java .util .ArrayList ;
1314import java .util .HashSet ;
1415import java .util .LinkedHashSet ;
1516import java .util .List ;
1617import java .util .Locale ;
18+ import java .util .Map ;
1719import java .util .Random ;
1820import java .util .Set ;
1921
@@ -29,6 +31,50 @@ public final class CatalogDataSeeder {
2931 "Tái bản chọn lọc" ,
3032 "Bản sưu tầm"
3133 };
34+ private static final Map <String , String > PRIORITY_GENERATED_COVERS = Map .ofEntries (
35+ Map .entry ("nghe-thuat/mau-sac-va-cam-xuc-an-ban-dac-biet" , "/images/books/generated/nghe-thuat-mau-sac-va-cam-xuc-an-ban-dac-biet.png" ),
36+ Map .entry ("nghe-thuat/anh-dep-quanh-ta-an-ban-dac-biet" , "/images/books/generated/nghe-thuat-anh-dep-quanh-ta-an-ban-dac-biet.png" ),
37+ Map .entry ("nghe-thuat/nhap-mon-hoi-hoa-an-ban-dac-biet" , "/images/books/generated/nghe-thuat-nhap-mon-hoi-hoa-an-ban-dac-biet.png" ),
38+ Map .entry ("nghe-thuat/so-tay-sang-tao" , "/images/books/generated/nghe-thuat-so-tay-sang-tao.png" ),
39+ Map .entry ("nghe-thuat/chat-lieu-va-anh-sang" , "/images/books/generated/nghe-thuat-chat-lieu-va-anh-sang.png" ),
40+ Map .entry ("nghe-thuat/bo-cuc-trong-thiet-ke" , "/images/books/generated/nghe-thuat-bo-cuc-trong-thiet-ke.png" ),
41+ Map .entry ("nghe-thuat/cau-chuyen-my-thuat" , "/images/books/generated/nghe-thuat-cau-chuyen-my-thuat.png" ),
42+ Map .entry ("nghe-thuat/nhiep-anh-duong-pho" , "/images/books/generated/nghe-thuat-nhiep-anh-duong-pho.png" ),
43+ Map .entry ("nghe-thuat/thiet-ke-tu-co-ban" , "/images/books/generated/nghe-thuat-thiet-ke-tu-co-ban.png" ),
44+ Map .entry ("nghe-thuat/ky-hoa-moi-ngay" , "/images/books/generated/nghe-thuat-ky-hoa-moi-ngay.png" ),
45+ Map .entry ("nghe-thuat/mau-sac-va-cam-xuc" , "/images/books/generated/nghe-thuat-mau-sac-va-cam-xuc.png" ),
46+ Map .entry ("nghe-thuat/anh-dep-quanh-ta" , "/images/books/generated/nghe-thuat-anh-dep-quanh-ta.png" ),
47+ Map .entry ("nghe-thuat/nhap-mon-hoi-hoa" , "/images/books/generated/nghe-thuat-nhap-mon-hoi-hoa.png" ),
48+ Map .entry ("am-thuc/cam-nang-lam-banh-an-ban-dac-biet" , "/images/books/generated/am-thuc-cam-nang-lam-banh-an-ban-dac-biet.png" ),
49+ Map .entry ("am-thuc/mon-viet-cuoi-tuan-an-ban-dac-biet" , "/images/books/generated/am-thuc-mon-viet-cuoi-tuan-an-ban-dac-biet.png" ),
50+ Map .entry ("am-thuc/bep-nha-an-yen-an-ban-dac-biet" , "/images/books/generated/am-thuc-bep-nha-an-yen-an-ban-dac-biet.png" ),
51+ Map .entry ("am-thuc/nghe-thuat-pha-che" , "/images/books/generated/am-thuc-nghe-thuat-pha-che.png" ),
52+ Map .entry ("am-thuc/thuc-don-30-ngay" , "/images/books/generated/am-thuc-thuc-don-30-ngay.png" ),
53+ Map .entry ("am-thuc/mon-ngon-dai-khach" , "/images/books/generated/am-thuc-mon-ngon-dai-khach.png" ),
54+ Map .entry ("am-thuc/huong-vi-mien-trung" , "/images/books/generated/am-thuc-huong-vi-mien-trung.png" ),
55+ Map .entry ("am-thuc/bep-chay-moi-ngay" , "/images/books/generated/am-thuc-bep-chay-moi-ngay.png" ),
56+ Map .entry ("am-thuc/bua-com-gia-dinh" , "/images/books/generated/am-thuc-bua-com-gia-dinh.png" ),
57+ Map .entry ("sach-giao-khoa/toan-lop-1" , "/images/books/generated/sach-giao-khoa-toan-lop-1.png" ),
58+ Map .entry ("phat-trien-ban-than/doc-vi-ban-than" , "/images/books/generated/phat-trien-ban-than-doc-vi-ban-than.png" ),
59+ Map .entry ("lich-su/lich-su-viet-nam" , "/images/books/generated/lich-su-lich-su-viet-nam.png" ),
60+ Map .entry ("tieu-thuyet/dac-nhan-tam" , "/images/books/generated/tieu-thuyet-dac-nhan-tam.png" ),
61+ Map .entry ("sach-thieu-nhi/co-be-ban-diem" , "/images/books/generated/sach-thieu-nhi-co-be-ban-diem.png" ),
62+ Map .entry ("tho/dac-nhan-tam" , "/images/books/generated/tho-dac-nhan-tam.png" ),
63+ Map .entry ("marketing/nghi-lon" , "/images/books/generated/marketing-nghi-lon.png" ),
64+ Map .entry ("lanh-dao/nghi-lon" , "/images/books/generated/lanh-dao-nghi-lon.png" ),
65+ Map .entry ("cong-nghe/co-the-cua-ban" , "/images/books/generated/cong-nghe-co-the-cua-ban.png" ),
66+ Map .entry ("sach-thieu-nhi/doraemon-tap-1-50" , "/images/books/generated/sach-thieu-nhi-doraemon-tap-1-50.png" ),
67+ Map .entry ("sach-giao-khoa/vat-ly-lop-10" , "/images/books/generated/sach-giao-khoa-vat-ly-lop-10.png" ),
68+ Map .entry ("sach-thieu-nhi/alice-o-xu-so-than-tien" , "/images/books/generated/sach-thieu-nhi-alice-o-xu-so-than-tien.png" ),
69+ Map .entry ("tho/harry-potter-va-hon-da-phu-thuy" , "/images/books/generated/tho-harry-potter-va-hon-da-phu-thuy.png" ),
70+ Map .entry ("van-hoc-co-dien/harry-potter-va-hon-da-phu-thuy" , "/images/books/generated/van-hoc-co-dien-harry-potter-va-hon-da-phu-thuy.png" ),
71+ Map .entry ("sach-ngoai-van/1984-george-orwell" , "/images/books/generated/sach-ngoai-van-1984-george-orwell.png" ),
72+ Map .entry ("am-thuc/cam-nang-lam-banh" , "/images/books/generated/am-thuc-cam-nang-lam-banh.png" ),
73+ Map .entry ("sach-ngoai-van/pride-and-prejudice" , "/images/books/generated/sach-ngoai-van-pride-and-prejudice.png" ),
74+ Map .entry ("lanh-dao/khoi-nghiep-tu-san-sau" , "/images/books/generated/lanh-dao-khoi-nghiep-tu-san-sau.png" ),
75+ Map .entry ("sach-thieu-nhi/harry-potter-bo-day-du" , "/images/books/generated/sach-thieu-nhi-harry-potter-bo-day-du.png" ),
76+ Map .entry ("khoa-hoc-tu-nhien/nguoi-dan-duong" , "/images/books/generated/khoa-hoc-tu-nhien-nguoi-dan-duong.png" )
77+ );
3278
3379 private CatalogDataSeeder () {
3480 }
@@ -354,7 +400,7 @@ private static List<Product> buildSampleProducts(List<Category> categorySlots, L
354400 boolean isFeatured = i < 3 ;
355401 boolean isBestseller = i < 4 ;
356402 boolean isNew = i >= 2 && i < 6 ;
357- String imageUrl = resolvePlaceholderPath (cat );
403+ String imageUrl = resolveProductImagePath (cat , title );
358404 String shortDesc = buildShortDescription (cat .getName (), author , publisher );
359405 String description = buildLongDescription (cat .getName (), author , publisher , pages );
360406
@@ -535,11 +581,27 @@ private static String resolvePlaceholderPath(Category category) {
535581 };
536582 }
537583
584+ private static String resolveProductImagePath (Category category , String title ) {
585+ String generatedCoverPath = resolveGeneratedCoverPath (category , title );
586+ if (generatedCoverPath != null ) {
587+ return generatedCoverPath ;
588+ }
589+
590+ return resolvePlaceholderPath (category );
591+ }
592+
538593 private static String resolveNormalizedImageUrl (Product product ) {
539594 if (product != null && ShowcaseBookCatalog .isCuratedIsbn (product .getIsbn ())) {
540595 return ShowcaseBookCatalog .localCoverPath (product .getIsbn ());
541596 }
542597
598+ String generatedCoverPath = resolveGeneratedCoverPath (
599+ product != null ? product .getCategory () : null ,
600+ product != null ? product .getName () : null );
601+ if (generatedCoverPath != null ) {
602+ return generatedCoverPath ;
603+ }
604+
543605 if (product != null && product .getImages () != null ) {
544606 for (String image : product .getImages ()) {
545607 if (image != null && !image .isBlank () && image .startsWith ("/" )) {
@@ -562,15 +624,22 @@ private static List<String> resolveNormalizedImages(Product product, String norm
562624 images .add (ShowcaseBookCatalog .localCoverPath (product .getIsbn ()));
563625 }
564626
627+ String generatedCoverPath = resolveGeneratedCoverPath (
628+ product != null ? product .getCategory () : null ,
629+ product != null ? product .getName () : null );
630+ if (generatedCoverPath != null ) {
631+ images .add (generatedCoverPath );
632+ }
633+
565634 if (product != null && product .getImages () != null ) {
566635 for (String image : product .getImages ()) {
567- if (image != null && ! image . isBlank () && image . startsWith ( "/" )) {
636+ if (isReusableLocalImagePath ( image , generatedCoverPath )) {
568637 images .add (image );
569638 }
570639 }
571640 }
572641
573- if (product != null && product . getImageUrl () != null && ! product .getImageUrl (). isBlank () && product . getImageUrl (). startsWith ( "/" )) {
642+ if (product != null && isReusableLocalImagePath ( product .getImageUrl (), generatedCoverPath )) {
574643 images .add (product .getImageUrl ());
575644 }
576645
@@ -580,4 +649,43 @@ private static List<String> resolveNormalizedImages(Product product, String norm
580649
581650 return List .copyOf (images );
582651 }
652+
653+ private static String resolveGeneratedCoverPath (Category category , String title ) {
654+ String key = generatedCoverKey (category , title );
655+ if (key .isBlank ()) {
656+ return null ;
657+ }
658+
659+ return PRIORITY_GENERATED_COVERS .get (key );
660+ }
661+
662+ private static String generatedCoverKey (Category category , String title ) {
663+ if (category == null || title == null || title .isBlank ()) {
664+ return "" ;
665+ }
666+
667+ return slugifyCoverKey (category .getName ()) + "/" + slugifyCoverKey (title );
668+ }
669+
670+ private static String slugifyCoverKey (String value ) {
671+ if (value == null || value .isBlank ()) {
672+ return "" ;
673+ }
674+
675+ return Normalizer .normalize (value , Normalizer .Form .NFD )
676+ .replaceAll ("\\ p{M}+" , "" )
677+ .replace ('\u0111' , 'd' )
678+ .replace ('\u0110' , 'D' )
679+ .toLowerCase (Locale .ROOT )
680+ .replaceAll ("[^a-z0-9]+" , "-" )
681+ .replaceAll ("(^-+|-+$)" , "" );
682+ }
683+
684+ private static boolean isReusableLocalImagePath (String image , String generatedCoverPath ) {
685+ if (image == null || image .isBlank () || !image .startsWith ("/" )) {
686+ return false ;
687+ }
688+
689+ return generatedCoverPath == null || !image .contains ("/images/books/placeholders/" );
690+ }
583691}
0 commit comments