[{"content":"Hoàn Thiện Layout DMG Bằng Tọa Độ Ở Phần 1, chúng ta đã giới thiệu công cụ create-dmg và chuẩn bị thư mục staging chỉ chứa .app cùng với ảnh nền.\nBây giờ là mảnh cuối cùng, layout. Mở cửa sổ DMG lớn bao nhiêu, và đặt biểu tượng ứng dụng và phím tắt Applications ở đâu — tất cả điều này được quyết định bởi tọa độ bạn truyền cho create-dmg. Trong bài viết này, chúng ta sẽ thiết kế những tọa độ đó từng cái một.\nNhư ở Phần 1, FocusTimer, giá trị tọa độ, v.v. đều là ví dụ.\nLệnh create-dmg Đầy Đủ Trước tiên, hãy xem toàn bộ lệnh hoàn chỉnh. Đây là lệnh tạo DMG cho ứng dụng mẫu FocusTimer 1.0:\ncreate-dmg \\ --volname \u0026#34;FocusTimer 1.0\u0026#34; \\ --background build/dmg-background.png \\ --window-pos 200 120 \\ --window-size 600 400 \\ --icon-size 100 \\ --icon \u0026#34;FocusTimer.app\u0026#34; 175 190 \\ --hide-extension \u0026#34;FocusTimer.app\u0026#34; \\ --app-drop-link 425 190 \\ --no-internet-enable \\ \u0026#34;build/FocusTimer-1.0.dmg\u0026#34; \\ \u0026#34;build/dmg-stage/\u0026#34; Hai dòng cuối cùng là đầu ra và đầu vào:\nbuild/FocusTimer-1.0.dmg — đường dẫn của file DMG sẽ được tạo build/dmg-stage/ — thư mục staging chỉ chứa .app từ Phần 1 Các dòng bắt đầu bằng -- ở trên là các tùy chọn thiết kế cửa sổ và biểu tượng. Hãy xem xét từng cái.\nHiểu Hệ Tọa Độ Trước khi xem các tùy chọn, hãy nắm rõ cách hoạt động của tọa độ.\nTất cả tọa độ vị trí của create-dmg đều là tọa độ bên trong cửa sổ DMG. Đơn vị là điểm (point) được giải thích ở Phần 1.\nGốc (0, 0) là góc trên trái của cửa sổ. X tăng khi bạn di chuyển sang phải, và Y tăng khi bạn di chuyển xuống dưới. (Lưu ý rằng, khác với đồ thị toán học, Y tăng xuống phía dưới.) Nếu bạn đặt kích thước cửa sổ là 600 400, tọa độ nằm trong phạm vi 0–600 theo chiều ngang và 0–400 theo chiều dọc. Tọa độ vị trí của một biểu tượng trỏ đến tâm của biểu tượng đó — không phải góc trên trái, mà là chính giữa.\nCác Tùy Chọn, Từng Cái Một Tùy chọn Giá trị (ví dụ) Ý nghĩa --volname \u0026quot;FocusTimer 1.0\u0026quot; Tên volume hiển thị khi DMG được mount --background build/dmg-background.png Ảnh nền cửa sổ (cái đã chuẩn bị ở Phần 1) --window-pos 200 120 Vị trí cửa sổ mở trên màn hình (X, Y) --window-size 600 400 Kích thước của cửa sổ (rộng, cao — theo điểm) --icon-size 100 Kích thước các biểu tượng bên trong cửa sổ (theo điểm) --icon \u0026quot;FocusTimer.app\u0026quot; 175 190 Vị trí của biểu tượng ứng dụng (X, Y trong cửa sổ) --hide-extension \u0026quot;FocusTimer.app\u0026quot; — Ẩn phần mở rộng .app trong tên biểu tượng --app-drop-link 425 190 Vị trí của phím tắt thư mục Applications (X, Y trong cửa sổ) --no-internet-enable — Tắt tính năng \u0026ldquo;internet-enable\u0026rdquo; không còn được dùng Một số đáng được gọi ra riêng.\n--window-pos vs. --window-size — hai cái này dễ nhầm lẫn. --window-pos là cửa sổ xuất hiện ở đâu trên màn hình (desktop) khi người dùng mở DMG, trong khi --window-size là kích thước của chính cửa sổ đó. Cái quan trọng cho thiết kế tọa độ là --window-size.\n--icon-size 100 — các biểu tượng bên trong cửa sổ được vẽ với kích thước 100 điểm. Bạn cũng cần biết giá trị này khi thiết kế ảnh nền, vì không gian 100 điểm mỗi biểu tượng chiếm (= 200 pixel trong ảnh nền) phải được để trống trong nền.\n--icon và --app-drop-link — đây là hai ngôi sao trái và phải của cửa sổ. --icon là biểu tượng ứng dụng người dùng phải kéo, và --app-drop-link là phím tắt thư mục Applications đóng vai trò điểm đến thả vào. Với --app-drop-link, không cần tạo symbolic link riêng — create-dmg tạo và đặt phím tắt Applications cho bạn.\n--hide-extension — không có cái này, biểu tượng hiển thị đầy đủ FocusTimer.app với phần mở rộng bên dưới. Ẩn nó đi làm cho nó chỉ hiển thị gọn là FocusTimer.\n--no-internet-enable — \u0026ldquo;internet-enable\u0026rdquo; là tính năng cũ mà Safari cũ tự động xử lý DMG đã tải xuống; ngày nay không còn được dùng nữa. Truyền tùy chọn này làm cho create-dmg bỏ qua bước xử lý đó và cảnh báo liên quan.\nThiết Kế Tọa Độ — Đặt Hai Biểu Tượng Bây giờ là phần cốt lõi. Chúng ta đặt kích thước cửa sổ là 600 400, vì vậy bên trong đó chúng ta quyết định đặt biểu tượng ứng dụng và phím tắt Applications ở đâu.\nTrong lệnh ví dụ, chúng ta đặt chúng như sau:\nBiểu tượng ứng dụng: --icon \u0026quot;FocusTimer.app\u0026quot; 175 190 Phím tắt Applications: --app-drop-link 425 190 Đây là ý nghĩa của các con số này:\nTrục X (ngang) — 175 và 425\nChiều rộng cửa sổ là 600 điểm, và tâm của nó là 300. Đặt hai biểu tượng ở 175 và 425 đặt mỗi cái 125 về phía trái và 125 về phía phải so với tâm. Nói cách khác, hai biểu tượng được đặt đối xứng quanh tâm. Với biểu tượng ứng dụng ở bên trái và Applications ở bên phải, điều này tạo ra hướng tự nhiên của \u0026ldquo;kéo từ trái sang phải\u0026rdquo;.\nTrục Y (dọc) — cả hai ở 190\nChiều cao cửa sổ là 400 điểm, và tâm của nó là 200. Đặt cả hai biểu tượng ở cùng 190 đặt chúng cạnh nhau ở cùng chiều cao, nằm hơi trên chính giữa (200). Vì nhãn biểu tượng được gắn bên dưới biểu tượng, việc nhích lên hơi một chút làm cho nó trông ở giữa về mặt thị giác khi đã bao gồm nhãn.\nKhông có một đáp án duy nhất cho tọa độ. Các giá trị trên chỉ là một ví dụ cân bằng để đặt \u0026ldquo;hai biểu tượng 100 điểm cạnh nhau và đối xứng trong cửa sổ 600×400\u0026rdquo;. Hãy tự do điều chỉnh theo thiết kế nền của bạn.\nCăn Chỉnh Ảnh Nền Với Tọa Độ Ở đây, quy ước @2x từ Phần 1 xuất hiện lại.\nTọa độ biểu tượng (175,190, 425,190) là tọa độ điểm của cửa sổ. Nhưng ảnh nền có kích thước gấp đôi — 1200×800 pixel. Khi bạn vẽ mũi tên trên ảnh nền, bạn phải nghĩ theo tọa độ pixel gấp đôi tọa độ điểm của cửa sổ.\nTọa độ pixel ảnh nền = tọa độ điểm cửa sổ × 2 Chuyển đổi hai biểu tượng từ ví dụ sang tọa độ pixel ảnh nền cho kết quả này:\nPhần tử Tọa độ cửa sổ (điểm) Tọa độ ảnh nền (pixel) Tâm biểu tượng ứng dụng (175, 190) (350, 380) Tâm Applications (425, 190) (850, 380) Vì vậy khi bạn vẽ mũi tên hướng dẫn trên ảnh nền (1200×800), nó phải được vẽ giữa pixel (350, 380) và (850, 380) — các vị trí của hai biểu tượng. Vì kích thước biểu tượng là 100 điểm = 200 pixel, hãy để trống vùng 200 pixel mỗi biểu tượng chiếm và đặt mũi tên trong không gian giữa chúng.\nCăn chỉnh artwork ảnh nền với tọa độ biểu tượng của create-dmg theo cùng một cơ sở tham chiếu là trái tim của thiết kế DMG. Nếu hai cái bị lệch, mũi tên sẽ chệch khỏi biểu tượng hoặc bị vẽ chồng lên chúng.\nXác Minh và Tinh Chỉnh Khi bạn chạy lệnh, build/FocusTimer-1.0.dmg được tạo ra. Nhấp đúp vào DMG kết quả và mở nó.\nẢnh nền có lấp đầy cửa sổ hoàn toàn không? (Nếu nó co cụm vào góc → đó là bẫy DPI từ Phần 1. Chuẩn hóa về 144 bằng sips.) Biểu tượng ứng dụng và Applications có được đặt gọn ở hai đầu mũi tên nền không? Nhãn biểu tượng có bị cắt hay chồng lên nhau không? Tọa độ thường không khớp hoàn hảo trong lần thử đầu tiên. Chỉ cần lặp lại quá trình vài lần: điều chỉnh các con số trong --icon và --app-drop-link một chút, chạy lại lệnh, và kiểm tra bằng mắt. Khi bạn đã xác định tọa độ cuối cùng, bạn hiếm khi cần thay đổi chúng nữa.\nTái Sử Dụng Cho Mỗi Lần Phát Hành — Tự Động Hóa Thiết kế DMG thực chất là công việc một lần. Sau khi bạn đã xác định ảnh nền và tọa độ, bạn tái sử dụng đúng các cài đặt đó cho mọi lần phát hành tiếp theo.\nVì vậy, tốt hơn là đặt lệnh create-dmg này vào một bước trong script build phát hành của bạn. Trong script, luồng thường trông như thế này:\nSao chép .app đã ký, công chứng và staple vào thư mục staging (Phần 1) Sao chép ảnh nền rồi chuẩn hóa DPI về 144 bằng sips (ngăn bẫy từ Phần 1) Chạy create-dmg với các tọa độ đã thiết kế ở trên Dọn dẹp file tạm thời như thư mục staging Chỉ có số phiên bản thay đổi từ lần phát hành này sang lần phát hành khác (số 1.0 trong --volname, và FocusTimer-1.0.dmg trong tên file đầu ra), vì vậy nếu bạn chỉ trích xuất phần đó ra thành biến script, bạn sẽ không cần chạm vào lệnh mỗi lần. Giữ đường dẫn ảnh nền và tất cả tọa độ là giá trị cố định.\nChúng tôi khuyên bạn nên để script tự động thực hiện chuẩn hóa DPI của ảnh nền (sips) mỗi lần. Theo cách đó, ngay cả khi bạn quên chuẩn hóa DPI khi bạn tái xuất bản thiết kế gốc và thay thế, script luôn sửa lại cho bạn.\nTổng Kết Series Thiết kế DMG hai phần đã hoàn tất. Bạn hiện có:\n✅ (Phần 1) create-dmg + thư mục staging .app + ảnh nền với @2x và DPI đã chuẩn hóa ✅ (Phần 2) Thiết kế tọa độ cửa sổ và biểu tượng + căn chỉnh tọa độ pixel nền với tọa độ điểm cửa sổ + tự động hóa Để tóm tắt lại các điểm mấu chốt:\nCửa sổ DMG là 600×400 điểm, và ảnh nền có kích thước gấp đôi — 1200×800 pixel (@2x). DPI của ảnh nền PNG phải được chuẩn hóa về 144 — chỉ khi đó ảnh nền mới lấp đầy cửa sổ chính xác. Tọa độ biểu tượng là tọa độ điểm trong cửa sổ, và tọa độ artwork nền là tọa độ pixel gấp đôi — căn chỉnh cả hai theo cùng một cơ sở tham chiếu. Đặt thiết kế bạn đã xác định vào script build và tái sử dụng nguyên trạng cho mỗi lần phát hành. Một mũi tên nhỏ, hay vị trí biểu tượng lệch vài điểm, có thể ảnh hưởng đến ấn tượng đầu tiên của người dùng. Vì cửa sổ .dmg là nơi người dùng lần đầu gặp ứng dụng của bạn, nó xứng đáng được thiết kế cẩn thận một lần.\nTài Liệu Tham Khảo create-dmg — GitHub sips — hướng dẫn lệnh macOS ","permalink":"https://hobbyworker.me/vi/dev/2026-05-21-design-macos-dmg-2-layout-coordinates/","summary":"\u003ch1 id=\"hoàn-thiện-layout-dmg-bằng-tọa-độ\"\u003eHoàn Thiện Layout DMG Bằng Tọa Độ\u003c/h1\u003e\n\u003cp\u003eỞ \u003ca href=\"../2026-05-20-design-macos-dmg-1-create-dmg-and-background/\"\u003ePhần 1\u003c/a\u003e, chúng ta đã giới thiệu công cụ \u003ccode\u003ecreate-dmg\u003c/code\u003e và chuẩn bị thư mục staging chỉ chứa \u003ccode\u003e.app\u003c/code\u003e cùng với ảnh nền.\u003c/p\u003e\n\u003cp\u003eBây giờ là mảnh cuối cùng, \u003cstrong\u003elayout\u003c/strong\u003e. Mở cửa sổ DMG lớn bao nhiêu, và đặt biểu tượng ứng dụng và phím tắt \u003ccode\u003eApplications\u003c/code\u003e ở đâu — tất cả điều này được quyết định bởi \u003cstrong\u003etọa độ\u003c/strong\u003e bạn truyền cho \u003ccode\u003ecreate-dmg\u003c/code\u003e. Trong bài viết này, chúng ta sẽ thiết kế những tọa độ đó từng cái một.\u003c/p\u003e","title":"Thiết Kế DMG Phân Phối Cho Ứng Dụng macOS (2): Tọa Độ Layout Cửa Sổ và Biểu Tượng, và Tự Động Hóa"},{"content":"Màn Hình Đầu Tiên Người Dùng Thấy Khi bạn tự phân phối ứng dụng macOS, người dùng tải file .dmg và nhấp đúp vào nó. Lúc đó, một cửa sổ Finder duy nhất mở ra. Cửa sổ này là màn hình đầu tiên nơi người dùng gặp ứng dụng của bạn.\nCửa sổ .dmg được thiết kế tốt thường trông như thế này — ảnh hướng dẫn lấp đầy nền, biểu tượng ứng dụng ở bên trái, và phím tắt đến thư mục Applications ở bên phải. Người dùng hoàn tất việc cài đặt bằng cách kéo biểu tượng ứng dụng sang Applications. Luồng \u0026ldquo;kéo để cài đặt\u0026rdquo; này thực chất là trải nghiệm cài đặt tiêu chuẩn cho các ứng dụng macOS indie.\nCửa sổ này không tự xuất hiện. Bạn phải tự định nghĩa ảnh nền, kích thước cửa sổ, và vị trí cùng kích thước của từng biểu tượng. Series này đề cập đến cách thiết kế cửa sổ .dmg đó.\nSeries này bắt đầu từ giả định rằng bạn đã có .app đã được ký, công chứng và staple — nói cách khác, ứng dụng cách phân phối một bước. Quá trình đó đã được đề cập trong series \u0026ldquo;Tự Phân Phối Ứng Dụng macOS\u0026rdquo;. .dmg là artifact của kênh phân phối trực tiếp (Developer ID) và không liên quan đến kênh Mac App Store.\nNhững Gì Series Này Xây Dựng Qua hai phần, bạn sẽ có:\n(Phần 1, bài này) Công cụ create-dmg + thư mục staging chỉ chứa .app + ảnh nền đã chuẩn bị (Phần 2) Thiết kế tọa độ của cửa sổ, biểu tượng và drop link + căn chỉnh ảnh nền với các tọa độ đó + tự động hóa Khi kết thúc series, bạn cần có:\nThư mục staging cô lập và chỉ chứa .app Ảnh nền tuân theo quy ước @2x Retina và có DPI được chuẩn hóa Lệnh create-dmg trong đó kích thước cửa sổ và vị trí biểu tượng đều được đặt theo tọa độ Dạng có thể tái sử dụng nguyên trạng cho mỗi lần phát hành Ứng Dụng Mẫu — FocusTimer Như trong các series trước, chúng ta sẽ dùng ứng dụng macOS giả tưởng FocusTimer (một ứng dụng đếm thời gian đơn giản để quản lý thời gian tập trung) làm ví dụ. FocusTimer, các đường dẫn, giá trị tọa độ, v.v. đều là ví dụ. Trong thực tế, hãy điều chỉnh theo ứng dụng của bạn.\nDMG Là Gì? DMG (.dmg) là file ảnh đĩa (disk image) của macOS. Hãy coi nó như một \u0026ldquo;đĩa ảo\u0026rdquo; đóng gói toàn bộ cấu trúc thư mục và file bên trong một file duy nhất. Khi người dùng nhấp đúp vào .dmg, macOS gắn kết (mount) nó như một đĩa, và nội dung của nó mở trong cửa sổ Finder.\nLý do dùng DMG để phân phối ứng dụng:\nỨng dụng (.app) thực ra là một thư mục. Khó đặt thư mục trực tiếp lên internet, nhưng bọc nó trong DMG biến nó thành một file duy nhất. Bạn có thể đặt phím tắt đến thư mục Applications bên trong cửa sổ DMG, hướng dẫn người dùng cài đặt bằng một cú kéo duy nhất. Bạn có thể thiết kế giao diện cửa sổ — nền, vị trí biểu tượng, v.v. — giúp trải nghiệm cài đặt trở nên gọn gàng. Công Cụ — create-dmg Tự tạo DMG bằng tay nghĩa là phải qua nhiều bước: tạo ảnh bằng hdiutil, mount nó, thao tác cửa sổ Finder bằng AppleScript để đặt các biểu tượng, rồi unmount lại. Đây là việc tẻ nhạt và dễ sai.\ncreate-dmg là công cụ mã nguồn mở gói gọn toàn bộ quá trình này thành một lệnh duy nhất. Truyền vào ảnh nền, kích thước cửa sổ và tọa độ biểu tượng dưới dạng tùy chọn, và nó tạo ra .dmg hoàn chỉnh cho bạn.\nBạn đã cài nó hồi thiết lập các công cụ tiên quyết trong Phần 1 của series \u0026ldquo;Tự Phân Phối Ứng Dụng macOS\u0026rdquo;. Nếu chưa, hãy cài bằng Homebrew:\nbrew install create-dmg Có hai dự án tên create-dmg. Dự án được dùng trong bài viết này là công cụ dòng lệnh được cài bằng Homebrew (create-dmg/create-dmg). Đừng nhầm với dự án riêng biệt có tên tùy chọn khác.\nBước 1 — Thư Mục Staging Chỉ Chứa .app DMG nên chứa chỉ một biểu tượng ứng dụng, gọn gàng. Nhưng khi bạn build và xuất ứng dụng, thư mục kết quả chứa không chỉ .app mà còn các file phụ như DistributionSummary.plist và log đóng gói. Nếu bạn biến thư mục đó trực tiếp thành DMG, những file phụ đó cũng bị lộ trong cửa sổ.\nVì vậy bạn chỉ sao chép .app vào thư mục trống để cô lập nó, rồi dùng thư mục đó làm nguyên liệu cho DMG. Thư mục cô lập này thường được gọi là thư mục staging.\n# Sao chép chỉ .app vào thư mục trống để không bị trộn với file phụ DMG_STAGE=\u0026#34;build/dmg-stage\u0026#34; rm -rf \u0026#34;$DMG_STAGE\u0026#34; mkdir -p \u0026#34;$DMG_STAGE\u0026#34; cp -R \u0026#34;build/export/FocusTimer.app\u0026#34; \u0026#34;$DMG_STAGE/\u0026#34; Bây giờ build/dmg-stage/ chỉ chứa FocusTimer.app. Khi bạn build DMG, trỏ create-dmg vào thư mục này, và cửa sổ sẽ chỉ hiển thị biểu tượng ứng dụng, gọn gàng. (Phím tắt thư mục Applications được tạo tự động qua tùy chọn create-dmg ở Phần 2.)\nBước 2 — Chuẩn Bị Ảnh Nền Trái tim của thiết kế cửa sổ DMG là ảnh nền. Đây là nơi mọi người thường bị mắc kẹt nhất, vì vậy hãy xem xét từng bước.\nKích Thước Canvas — Tại Sao 1200×800? Ở Phần 2, bạn sẽ đặt kích thước cửa sổ DMG bằng cách truyền --window-size 600 400 vào create-dmg. Ở đây, đơn vị của 600 và 400 không phải pixel mà là điểm (point). Điểm là đơn vị tọa độ logic mà macOS sử dụng.\nTrên màn hình Retina, 1 điểm được vẽ bằng 2 pixel. Vì vậy để lấp đầy cửa sổ 600×400 điểm hoàn toàn và sắc nét, ảnh nền phải có kích thước gấp đôi — 1200×800 pixel. Đây là quy ước @2x (độ phân giải gấp đôi).\nTóm lại — cửa sổ là 600×400 điểm, và ảnh nền là 1200×800 pixel. Mối quan hệ 2× này trở lại là yếu tố quan trọng khi bạn thiết lập tọa độ ở Phần 2.\nVẽ Gì Trên Nền? Đây là một quan niệm sai lầm phổ biến đáng đề cập. Đừng vẽ biểu tượng ứng dụng hay biểu tượng thư mục Applications vào ảnh nền. Các biểu tượng đó được create-dmg đặt lên cửa sổ dưới dạng file thực tế (Phần 2).\nNhững gì được đưa vào ảnh nền chỉ là các yếu tố trang trí và hướng dẫn:\nMũi tên trỏ từ biểu tượng ứng dụng về phía thư mục Applications — thiết kế phổ biến nhất để hướng mắt và tay người dùng đến \u0026ldquo;kéo về phía này\u0026rdquo;. Nếu cần, một cụm từ hướng dẫn ngắn như \u0026ldquo;Kéo vào đây\u0026rdquo; Màu thương hiệu hoặc họa tiết nền tinh tế Nói cách khác, ảnh nền là \u0026ldquo;bức tranh để trống những chỗ biểu tượng sẽ ngồi, và chỉ vẽ đường hướng dẫn nối chúng lại\u0026rdquo;. Bạn để trống hai vị trí biểu tượng trái và phải — mỗi cái 100 điểm = 200 pixel (ở Phần 2) — và đặt mũi tên ở giữa chúng.\nBẫy Quan Trọng — DPI Bẫy mà mọi người hay gặp nhất với ảnh nền là DPI (pixel trên mỗi inch).\nKhi bạn xuất PNG từ trình chỉnh sửa ảnh, file lưu trữ không chỉ số pixel (1200×800) mà còn giá trị DPI. Khi Finder vẽ nền DMG, nó nhìn vào DPI này để tính ngược \u0026ldquo;ảnh này có bao nhiêu điểm\u0026rdquo;. Phép tính xấp xỉ như sau:\nKích thước điểm = số pixel ÷ (DPI ÷ 72) Nếu DPI là 144: 1200 ÷ (144 ÷ 72) = 1200 ÷ 2 = 600 điểm. Nó lấp đầy cửa sổ (600pt) chính xác. ✅ Nhưng một số trình chỉnh sửa ảnh (ví dụ: Pixelmator Pro) đóng dấu giá trị DPI tùy ý (ví dụ: 338) khi xuất. Nếu DPI là 338: 1200 ÷ (338 ÷ 72) ≈ 256 điểm. Điều đó nhỏ hơn nhiều so với cửa sổ (600pt), vì vậy nền được vẽ co cụm vào góc trên trái của cửa sổ. ❌ Số pixel trong cả hai trường hợp đều là 1200×800, nhưng nền bị hỏng vì một giá trị DPI. Lần đầu tiên bạn gặp điều này, nguyên nhân thực sự khó tìm.\nGiải pháp là buộc DPI của PNG đã xuất về 144. Một dòng với lệnh sips, được tích hợp mặc định trong macOS, là đủ:\nsips -s dpiWidth 144 -s dpiHeight 144 dmg-background.png Sau khi chạy một dòng này, dù trình chỉnh sửa đã đóng dấu DPI gì, nó được đặt về 144, và 1200×800 pixel được hiểu là chính xác 600×400 điểm.\nĐiểm mấu chốt — ① tạo ảnh nền với 1200×800 pixel, và ② sau khi xuất, chuẩn hóa DPI về 144 bằng sips. Tuân theo hai quy tắc này và bạn sẽ tránh được bẫy DPI.\nTổng Kết Phần 1 Nếu bạn đã làm theo đến đây, bây giờ bạn có:\n✅ Công cụ create-dmg đã cài đặt + hiểu vai trò của nó ✅ Thư mục staging cô lập và chỉ chứa .app (build/dmg-stage/) ✅ Ảnh nền được tạo với 1200×800 pixel và DPI đã được chuẩn hóa về 144 Các nguyên liệu đã sẵn sàng. Nhưng bạn vẫn chưa có cửa sổ để hiển thị ảnh nền và đặt các biểu tượng. Mở cửa sổ lớn bao nhiêu, và đặt biểu tượng ứng dụng và phím tắt Applications ở đâu — tất cả điều này được quyết định bởi tọa độ bạn truyền cho create-dmg.\nỞ phần tiếp theo, chúng ta sẽ thiết kế những tọa độ đó từng cái một, đề cập cách căn chỉnh tọa độ pixel của ảnh nền với tọa độ điểm của cửa sổ để mũi tên trong ảnh nền nằm chính xác giữa hai biểu tượng, và cách tự động hóa quá trình này để có thể tái sử dụng cho mỗi lần phát hành.\n","permalink":"https://hobbyworker.me/vi/dev/2026-05-20-design-macos-dmg-1-create-dmg-and-background/","summary":"\u003ch1 id=\"màn-hình-đầu-tiên-người-dùng-thấy\"\u003eMàn Hình Đầu Tiên Người Dùng Thấy\u003c/h1\u003e\n\u003cp\u003eKhi bạn tự phân phối ứng dụng macOS, người dùng tải file \u003ccode\u003e.dmg\u003c/code\u003e và nhấp đúp vào nó. Lúc đó, \u003cstrong\u003emột cửa sổ Finder duy nhất mở ra.\u003c/strong\u003e Cửa sổ này là màn hình đầu tiên nơi người dùng gặp ứng dụng của bạn.\u003c/p\u003e\n\u003cp\u003eCửa sổ \u003ccode\u003e.dmg\u003c/code\u003e được thiết kế tốt thường trông như thế này — ảnh hướng dẫn lấp đầy nền, biểu tượng ứng dụng ở bên trái, và phím tắt đến thư mục \u003ccode\u003eApplications\u003c/code\u003e ở bên phải. Người dùng hoàn tất việc cài đặt bằng cách \u003cstrong\u003ekéo\u003c/strong\u003e biểu tượng ứng dụng sang \u003ccode\u003eApplications\u003c/code\u003e. Luồng \u0026ldquo;kéo để cài đặt\u0026rdquo; này thực chất là trải nghiệm cài đặt tiêu chuẩn cho các ứng dụng macOS indie.\u003c/p\u003e","title":"Thiết Kế DMG Phân Phối Cho Ứng Dụng macOS (1): create-dmg và Chuẩn Bị Ảnh Nền"},{"content":"Con Đường Gửi Build Lên App Store Ở Phần 1 chúng ta đã tạo MAS build target, và ở Phần 2 chúng ta đã tạo các file cấu hình và phân nhánh code phân tách hai kênh. Target FocusTimer MAS hiện ở dạng có thể đưa lên App Store.\nTrong phần cuối này, chúng ta sẽ thiết lập con đường để upload build đó lên App Store Connect, và đề cập đến cách xác minh hai kênh để chúng luôn không bị hỏng, kết thúc series.\nNhư ở Phần 1 và 2, FocusTimer, com.example.FocusTimer.mas, Team ID ABCDE12345, v.v. đều là giá trị mẫu.\nBước 1 — ExportOptions-MAS.plist Để Upload Trong series phân phối trực tiếp, chúng ta đã lưu ý rằng khi xuất archive để phân phối, lệnh xcodebuild -exportArchive đọc ExportOptions.plist. Kênh MAS cần một file cấu hình xuất riêng biệt.\nTạo ExportOptions-MAS.plist trong thư mục release của dự án.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;method\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;app-store\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;destination\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;upload\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;signingStyle\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;automatic\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;teamID\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;ABCDE12345\u0026lt;/string\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; Đây là ý nghĩa của từng khóa.\nmethod = app-store — Có nghĩa là build này được xuất cho kênh Mac App Store. Điều này trái ngược với developer-id của kênh phân phối trực tiếp. destination = upload — Làm cho xcodebuild -exportArchive upload artifact đã xuất thẳng đến App Store Connect. Không cần đi qua Transporter hay công cụ upload riêng biệt như trước đây. signingStyle = automatic — Xcode tự động khớp provisioning profile Mac App Store và chứng chỉ Apple Distribution. teamID — Apple Developer Team ID của bạn. Sau khi tạo file này, bạn tái sử dụng nó cho mọi lần phát hành MAS. Hãy để ExportOptions.plist cho phân phối trực tiếp như cũ, và giữ file này nằm cạnh nó.\nBước 2 — Đăng Ký App Record Trong App Store Connect Để thực sự nộp ứng dụng để xét duyệt, phải có record ứng dụng trong App Store Connect. Một record là vỏ chứa tất cả thông tin hiển thị trong cửa hàng — tên ứng dụng, mô tả, ảnh chụp màn hình, giá cả, v.v.\nTạo record có thể đợi đến ngay trước lần nộp thực tế đầu tiên. Ở giai đoạn thiết lập một lần, chỉ cần biết trước \u0026ldquo;những mục nào cần quyết định và như thế nào\u0026rdquo; là đủ. Đặc biệt, Primary Language bên dưới rất khó hoàn tác sau khi đã đặt, vì vậy hãy quyết định cẩn thận trước.\nTrong App Store Connect → My Apps → + → New App, nhập những thông tin sau.\nPlatform — Chọn macOS Primary Language (Ngôn ngữ chính) — ⚠️ Mục cần quyết định cẩn thận nhất. Sau khi đặt, rất khó thay đổi qua self-service. Nếu bạn có kế hoạch phát hành ứng dụng ở nhiều quốc gia, thường đặt Tiếng Anh (English, U.S.) làm ngôn ngữ chính, vì ngôn ngữ chính là \u0026ldquo;ngôn ngữ cơ sở hiển thị khi không có bản dịch cho một quốc gia cụ thể\u0026rdquo;. App Name (Tên ứng dụng) — Tên bằng ngôn ngữ chính (ví dụ: FocusTimer). Tên cho các ngôn ngữ khác được thêm sau riêng biệt như bản địa hóa theo từng ngôn ngữ. Ví dụ, bạn có thể làm cho người dùng Hàn Quốc thấy tên tiếng Hàn và người dùng Nhật Bản thấy tên tiếng Nhật. Bundle ID — Chọn com.example.FocusTimer.mas từ dropdown. Đó là ID bạn đã đăng ký ở Phần 1. Đừng nhầm với Bundle ID phân phối trực tiếp (com.example.FocusTimer) — phải là cái có .mas được thêm vào. SKU — Chuỗi định danh nội bộ chỉ do bạn sử dụng. Nó không được hiển thị trong cửa hàng, vì vậy bạn có thể chọn tự do (ví dụ: focustimer-mas-001). Danh mục ứng dụng phải khớp với giá trị bạn đã đặt vào LSApplicationCategoryType trong Info.plist ở Phần 2. Nếu bạn đặt public.app-category.productivity (Năng suất) trong plist, bạn cũng phải chọn danh mục \u0026ldquo;Năng suất\u0026rdquo; trong App Store Connect để cảnh báo không khớp không xuất hiện trong quá trình xét duyệt.\nBước 3 — Xác Minh Cả Hai Build Kênh Bây giờ bạn có một codebase với hai build target. Điều này đi kèm chi phí bảo trì. Nếu bạn thường chỉ build kênh phân phối trực tiếp, bạn sẽ không nhận ra nếu MAS target đã bị hỏng âm thầm. Ví dụ, bạn thêm code mới nhưng quên phân nhánh #if canImport(Sparkle), và chỉ MAS build không biên dịch được.\nCách đáng tin cậy nhất để ngăn điều này là archive cả MAS target mỗi khi bạn thực hiện phát hành phân phối trực tiếp. Nếu build thành công, nghĩa là việc phân nhánh của hai kênh vẫn còn nguyên vẹn.\nxcodebuild -project FocusTimer.xcodeproj -scheme \u0026#34;FocusTimer MAS\u0026#34; \\ -configuration Release \\ -destination \u0026#39;platform=macOS\u0026#39; \\ -archivePath build/FocusTimer-MAS.xcarchive \\ archive Nếu bạn thấy điều sau ở cuối đầu ra, việc phân nhánh MAS vẫn ổn.\n** ARCHIVE SUCCEEDED ** Tôi khuyên bạn nên đặt lệnh này vào bước cuối cùng của script tự động hóa phát hành để nó tự động chạy mỗi lần phát hành. Ngay cả khi bạn thực sự không upload MAS build lên App Store lúc đó, chỉ cần xác nhận rằng archive thành công là đủ để đảm bảo rằng việc phân nhánh của hai kênh chưa bị hỏng.\nTổng Kết Series — Hoàn Tất Thiết Lập Một Lần Cho Phát Hành MAS Phần thiết lập một lần qua ba phần để phát hành lên Mac App Store hiện đã hoàn toàn xong. Bạn hiện có:\n✅ (Phần 1) Bundle ID chỉ dành cho MAS + build target FocusTimer MAS được nhân đôi ✅ (Phần 2) Entitlements và Info.plist chỉ dành cho MAS + cài đặt build + phân nhánh code #if canImport(Sparkle) ✅ (Phần 3) ExportOptions-MAS.plist để upload + cách đăng ký App Store Connect record + xác minh cả hai build kênh Với điều này, một ứng dụng macOS duy nhất hiện có cả kênh phân phối trực tiếp (Developer ID) và Mac App Store. Để tóm tắt lại cấu trúc cốt lõi:\nChia sẻ cùng một codebase, nhưng tách thành hai build target. Target phân phối trực tiếp bao gồm Sparkle, và MAS target loại trừ nó. Sự khác biệt giữa hai target được thể hiện qua các file entitlements, Info.plist và ExportOptions riêng biệt và phân nhánh code #if canImport(Sparkle). Thiết lập ban đầu đòi hỏi nhiều công sức, nhưng đây cũng là cấu trúc tiếp tục được tái sử dụng sau khi đã thiết lập xong. Từ đó, khi bạn thực hiện phát hành phân phối trực tiếp, bạn chỉ cần archive MAS build cùng lúc và xác nhận việc phân nhánh vẫn còn hoạt động. Việc nộp App Store thực tế (ảnh chụp màn hình, mô tả, xử lý xét duyệt) là công việc riêng biệt được thực hiện trên phần thiết lập một lần này, và đó là chủ đề cho một bài viết khác.\nTài Liệu Tham Khảo App Store Connect Help LSApplicationCategoryType — Apple Developer Documentation Distributing your app for beta testing and releases — Apple Developer ","permalink":"https://hobbyworker.me/vi/dev/2026-05-19-distribute-macos-app-mas-3-export-and-app-store-connect/","summary":"\u003ch1 id=\"con-đường-gửi-build-lên-app-store\"\u003eCon Đường Gửi Build Lên App Store\u003c/h1\u003e\n\u003cp\u003eỞ \u003ca href=\"../2026-05-17-distribute-macos-app-mas-1-target-setup/\"\u003ePhần 1\u003c/a\u003e chúng ta đã tạo MAS build target, và ở \u003ca href=\"../2026-05-18-distribute-macos-app-mas-2-build-config-and-code/\"\u003ePhần 2\u003c/a\u003e chúng ta đã tạo các file cấu hình và phân nhánh code phân tách hai kênh. Target \u003ccode\u003eFocusTimer MAS\u003c/code\u003e hiện ở dạng có thể đưa lên App Store.\u003c/p\u003e\n\u003cp\u003eTrong phần cuối này, chúng ta sẽ thiết lập \u003cstrong\u003econ đường để upload build đó lên App Store Connect\u003c/strong\u003e, và đề cập đến cách xác minh hai kênh để chúng luôn không bị hỏng, kết thúc series.\u003c/p\u003e","title":"Phát Hành Ứng Dụng macOS Lên Mac App Store (3): Cài Đặt Upload và Đăng Ký App Store Connect"},{"content":"Làm Cho Target Thực Sự \u0026ldquo;Dành Riêng Cho MAS\u0026rdquo; Ở Phần 1, chúng ta đã đăng ký Bundle ID chỉ dành cho MAS và nhân đôi build target FocusTimer MAS. Nhưng target đó vẫn chỉ là bản sao của target phân phối trực tiếp.\nMAS build phải khác với build phân phối trực tiếp ở ba điểm.\nEntitlements — chỉ bộ quyền hạn tối thiểu phù hợp cho App Store Info.plist — bỏ các khóa Sparkle, thêm metadata App Store Code — phân nhánh để biên dịch được ngay cả khi không có Sparkle Trong bài viết này, chúng ta sẽ tách cả ba điều đó.\nNhư ở Phần 1, FocusTimer, com.example.FocusTimer.mas, v.v. đều là giá trị mẫu.\nBước 1 — File Entitlements Chỉ Dành Cho MAS Entitlements là file liệt kê các quyền hệ thống mà ứng dụng yêu cầu. Trong build phân phối trực tiếp, Sparkle cần các quyền bổ sung để cài đặt bản cập nhật, nhưng MAS build không có Sparkle, vì vậy những quyền đó là không cần thiết.\nTạo file FocusTimer-MAS.entitlements mới trong thư mục gốc của dự án.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;com.apple.security.app-sandbox\u0026lt;/key\u0026gt; \u0026lt;true/\u0026gt; \u0026lt;key\u0026gt;com.apple.security.files.user-selected.read-write\u0026lt;/key\u0026gt; \u0026lt;true/\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; So với FocusTimer.entitlements cho phân phối trực tiếp, đây là sự khác biệt.\nKhông có quyền liên quan đến Sparkle — Các mục temporary-exception.mach-lookup.* từ build phân phối trực tiếp (ngoại lệ cho phép Sparkle giao tiếp với installation helper) không có lý do gì để tồn tại trong MAS build. Không có network.client — Khi Sparkle bị xóa, bản thân ứng dụng FocusTimer không thực hiện bất kỳ cuộc gọi mạng nào. Thành thật mà bỏ đi các quyền bạn không dùng, và các quyền không cần thiết cũng bị gắn cờ trong quá trình xét duyệt. Bề mặt quyền hạn ứng dụng yêu cầu càng nhỏ càng tốt. Ví dụ trên là dạng đơn giản nhất có thể, chỉ đủ quyền để \u0026ldquo;đọc và ghi các file mà người dùng chọn rõ ràng\u0026rdquo;. Nếu ứng dụng của bạn thực sự dùng mạng hay tài nguyên khác, hãy thêm quyền tương ứng, nhưng không bao giờ bao gồm quyền bạn không dùng.\nBước 2 — File Info.plist Chỉ Dành Cho MAS Tương tự, tạo FocusTimer-MAS-Info.plist mới trong thư mục gốc của dự án.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;LSApplicationCategoryType\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;public.app-category.productivity\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;ITSAppUsesNonExemptEncryption\u0026lt;/key\u0026gt; \u0026lt;false/\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; Có hai điểm khác biệt so với FocusTimer-Info.plist cho phân phối trực tiếp.\n① Tất cả các khóa Sparkle SU* đều bị bỏ\nInfo.plist cho phân phối trực tiếp chứa các khóa cấu hình Sparkle như SUFeedURL và SUPublicEDKey. Info.plist MAS không được chứa bất kỳ khóa nào trong số này. Việc xét duyệt App Store cấm cập nhật tự động tích hợp sẵn, vì vậy ngay cả một khóa chỉ gợi ý đến tính năng như vậy còn lại trong plist cũng có thể là vấn đề. An toàn nhất là không đưa khóa vào luôn.\n② Các khóa metadata App Store được thêm vào\nLSApplicationCategoryType — danh mục ứng dụng thuộc về trên App Store. public.app-category.productivity trong ví dụ trên có nghĩa là danh mục \u0026ldquo;Năng suất\u0026rdquo;. Giá trị này phải khớp với danh mục bạn đặt trong App Store Connect ở Phần 3, để cảnh báo không khớp không xuất hiện trong quá trình xét duyệt. ITSAppUsesNonExemptEncryption — liệu ứng dụng có sử dụng công nghệ mã hóa tuân theo quy định xuất khẩu hay không. Nếu bạn chỉ dùng mã hóa tiêu chuẩn (ví dụ: HTTPS, API hệ thống tiêu chuẩn) hoặc không dùng mã hóa, bạn có thể đặt thành false. Nhúng cứng khóa này trước sẽ tự động vượt qua bảng câu hỏi mã hóa xuất hiện mỗi khi bạn upload build. Trước khi đặt ITSAppUsesNonExemptEncryption thành false, hãy xác nhận rằng ứng dụng của bạn thực sự không dùng mã hóa phi tiêu chuẩn. Nếu bạn không chắc, an toàn hơn khi tham khảo tài liệu của Apple về mã hóa.\nBước 3 — Cài Đặt Build MAS Target Bây giờ là cập nhật cài đặt build mà chúng ta đã trì hoãn từ Phần 1, Mục 2-3. Trong TARGETS → FocusTimer MAS → Build Settings, hãy căn chỉnh những thứ sau.\nKhóa cài đặt build Giá trị PRODUCT_BUNDLE_IDENTIFIER com.example.FocusTimer.mas INFOPLIST_FILE FocusTimer-MAS-Info.plist CODE_SIGN_ENTITLEMENTS FocusTimer-MAS.entitlements ENABLE_APP_SANDBOX YES ENABLE_HARDENED_RUNTIME YES ENABLE_USER_SELECTED_FILES readwrite CODE_SIGN_STYLE Automatic MARKETING_VERSION, CURRENT_PROJECT_VERSION Giống như target phân phối trực tiếp Các điểm mấu chốt như sau.\nINFOPLIST_FILE và CODE_SIGN_ENTITLEMENTS phải trỏ đến các file chỉ dành cho MAS bạn vừa tạo. Lỗi thường gặp là để hai dòng này trỏ đến các file phân phối trực tiếp. Đặt CODE_SIGN_STYLE thành Automatic để Xcode tự động cấp và khớp provisioning profile Mac App Store và chứng chỉ Apple Distribution. Bạn không cần tự quản lý việc ký. Nếu ứng dụng không dùng mạng, không đặt ENABLE_OUTGOING_NETWORK_CONNECTIONS. Đây là cùng lý do như việc bỏ network.client khỏi entitlements. Giữ số phiên bản (MARKETING_VERSION, v.v.) ở cùng giá trị với target phân phối trực tiếp, để phiên bản của hai kênh không bị lệch nhau. Bước 4 — Phân Nhánh Code Sparkle (#if canImport(Sparkle)) Ngay cả sau khi tách các file cấu hình, vẫn còn một vấn đề. Đâu đó trong mã nguồn có import Sparkle và code sử dụng Sparkle, nhưng Sparkle không được liên kết vào MAS target, vì vậy chính câu lệnh import đó tạo ra lỗi biên dịch.\nGiải pháp là bọc code liên quan đến Sparkle bằng chỉ thị biên dịch có điều kiện của Swift #if canImport(Sparkle).\nTại Sao canImport — Tại Sao Không Dùng Flag Riêng Bạn cũng có thể phân nhánh bằng flag biên dịch tùy chỉnh như #if MAS_BUILD. Nhưng khi đó bạn sẽ phải đồng bộ thủ công cài đặt \u0026ldquo;bật flag cho MAS target, tắt cho direct distribution target\u0026rdquo;. Điều đó dễ bị lệch khi target nhân lên hoặc cài đặt thay đổi.\ncanImport(Sparkle) thì khác. Nó có trình biên dịch trực tiếp kiểm tra xem \u0026ldquo;Sparkle.framework có được liên kết vào target này không\u0026rdquo;. Vì chúng ta đã xóa Sparkle khỏi package dependency của MAS target ở Phần 1, canImport(Sparkle) tự động là false trong MAS target. Không cần flag riêng để đồng bộ. Đó là lý do tại sao các ứng dụng macOS dùng pattern này như tiêu chuẩn thực tế khi tách kênh MAS.\nPhân Nhánh ① — Bọc Toàn Bộ File Chỉ Dành Cho Sparkle File chỉ chứa logic cập nhật tự động (ví dụ: UpdaterCoordinator.swift) được bọc toàn bộ file bằng #if.\n#if canImport(Sparkle) import Foundation import Sparkle import Combine @Observable @MainActor final class UpdaterCoordinator { // Code bọc Sparkle updater … } #endif Trong MAS build, file này được biên dịch như thể nó trống và không có tác dụng gì cả.\nPhân Nhánh ② — Bọc Cả Những Nơi Dùng Sparkle Code tạo UpdaterCoordinator và truyền nó cho UI cũng cần được phân nhánh. Nếu không, MAS build sẽ tham chiếu đến \u0026ldquo;loại không tồn tại\u0026rdquo;.\n@main struct FocusTimerApp: App { @State private var viewModel = CompositionRoot.makeContentViewModel() #if canImport(Sparkle) @State private var updater = UpdaterCoordinator() #endif var body: some Scene { Settings { #if canImport(Sparkle) PreferenceView(viewModel: viewModel, updater: updater) #else PreferenceView(viewModel: viewModel) #endif } MenuBarExtra { #if canImport(Sparkle) MenuBarContent(updater: updater) #else MenuBarContent() #endif } label: { // Biểu tượng menu bar … } } } private struct MenuBarContent: View { #if canImport(Sparkle) @Bindable var updater: UpdaterCoordinator #endif var body: some View { Button(\u0026#34;Hiển thị FocusTimer\u0026#34;) { /* … */ } #if canImport(Sparkle) Button(\u0026#34;Kiểm tra cập nhật…\u0026#34;) { updater.checkForUpdates() } .disabled(!updater.canCheckForUpdates) #endif // Các mục menu chung khác … } } Có ba điểm mấu chốt.\nChính khai báo field updater được bọc trong #if. Bất kỳ view nhận updater nào (PreferenceView, MenuBarContent) đều được phân nhánh thành hai dạng — một khi có Sparkle và một khi không có. Các phần tử UI chỉ dành cho Sparkle như \u0026ldquo;Kiểm tra cập nhật…\u0026rdquo; cũng được bọc trong #if. Trong MAS build, mục menu này không xuất hiện gì cả — và đúng vậy, phiên bản App Store không nên có menu tự cập nhật. Các phần tử như toggle \u0026ldquo;Bật cập nhật tự động\u0026rdquo; trong màn hình cài đặt (PreferenceView) cũng nên được bọc bằng cùng pattern.\nTổng Kết Phần 2 Nếu bạn đã làm theo đến đây, bây giờ bạn có:\n✅ File entitlements chỉ dành cho MAS FocusTimer-MAS.entitlements (quyền hạn tối thiểu) ✅ FocusTimer-MAS-Info.plist chỉ dành cho MAS (đã xóa khóa Sparkle, đã thêm metadata App Store) ✅ Cài đặt build của MAS target đã được sắp xếp gọn gàng ✅ Code cập nhật tự động được phân nhánh bằng #if canImport(Sparkle) Bây giờ target FocusTimer MAS thực sự khác với target phân phối trực tiếp — nó ở dạng có thể đưa lên App Store. Điều còn lại là thiết lập con đường để thực sự upload build này lên App Store Connect.\nỞ phần tiếp theo, chúng ta sẽ kết thúc series bằng cách tạo ExportOptions-MAS.plist để upload, đăng ký app record trong App Store Connect, và hướng dẫn cách xác minh để hai kênh luôn không bị hỏng.\n","permalink":"https://hobbyworker.me/vi/dev/2026-05-18-distribute-macos-app-mas-2-build-config-and-code/","summary":"\u003ch1 id=\"làm-cho-target-thực-sự-dành-riêng-cho-mas\"\u003eLàm Cho Target Thực Sự \u0026ldquo;Dành Riêng Cho MAS\u0026rdquo;\u003c/h1\u003e\n\u003cp\u003eỞ \u003ca href=\"../2026-05-17-distribute-macos-app-mas-1-target-setup/\"\u003ePhần 1\u003c/a\u003e, chúng ta đã đăng ký Bundle ID chỉ dành cho MAS và nhân đôi build target \u003ccode\u003eFocusTimer MAS\u003c/code\u003e. Nhưng target đó vẫn chỉ là bản sao của target phân phối trực tiếp.\u003c/p\u003e\n\u003cp\u003eMAS build phải khác với build phân phối trực tiếp ở ba điểm.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eEntitlements\u003c/strong\u003e — chỉ bộ quyền hạn tối thiểu phù hợp cho App Store\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInfo.plist\u003c/strong\u003e — bỏ các khóa Sparkle, thêm metadata App Store\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCode\u003c/strong\u003e — phân nhánh để biên dịch được ngay cả khi không có Sparkle\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eTrong bài viết này, chúng ta sẽ tách cả ba điều đó.\u003c/p\u003e","title":"Phát Hành Ứng Dụng macOS Lên Mac App Store (2): Tách Cấu Hình và Code Giữa Hai Kênh"},{"content":"Kênh Phân Phối Khác — Mac App Store Series trước đã đề cập đến phần thiết lập một lần để phân phối trực tiếp ứng dụng macOS bằng Developer ID. Khi bạn đã có chứng chỉ, công chứng, cập nhật tự động Sparkle và update feed được lưu trữ, bạn có thể cho người dùng tải file .dmg trực tiếp mà không cần qua App Store.\nSeries này đề cập đến phần thiết lập một lần để đưa cùng ứng dụng đó lên Mac App Store (MAS). Hai phương thức phân phối không phải là sự lựa chọn một trong hai. Bạn có thể vận hành một ứng dụng qua cả kênh phân phối trực tiếp lẫn kênh App Store cùng một lúc. App Store có lợi thế là Apple xử lý thanh toán, hoàn tiền và khả năng hiển thị tìm kiếm thay bạn, và nó mang lại độ tin cậy cao hơn từ người dùng, vì vậy việc chạy song song với phân phối trực tiếp là lựa chọn phổ biến.\nSeries này giả sử ứng dụng của bạn đã được phân phối trực tiếp bằng Developer ID và đã tích hợp cập nhật tự động Sparkle. Phần thiết lập đó đã được đề cập bắt đầu từ Phần 1 của series \u0026ldquo;Tự Phân Phối Ứng Dụng macOS\u0026rdquo;. Nếu bạn chưa đọc, tôi khuyên bạn nên đọc trước.\nTại Sao MAS Cần \u0026ldquo;Target Khác\u0026rdquo; Ứng dụng phân phối trực tiếp bao gồm cập nhật tự động Sparkle — tính năng cho phép ứng dụng tự kiểm tra update feed, tải phiên bản mới và thay thế chính nó.\nNhưng quy tắc duyệt Mac App Store cấm hành vi này. Ứng dụng trên App Store không được tải code từ bên ngoài để tự cập nhật; cập nhật phải chỉ diễn ra qua App Store. Nói cách khác, build dự định cho MAS không được chứa Sparkle.\nTuy nhiên, build phân phối trực tiếp phải có Sparkle. Một artifact build duy nhất không thể đáp ứng cả hai yêu cầu cùng một lúc. Vì vậy giải pháp là:\nMột codebase, hai build target.\nKênh Build target Sparkle Phân phối qua Developer ID FocusTimer Có Phân phối trực tiếp (.dmg) Mac App Store FocusTimer MAS Không App Store Bạn chia sẻ một codebase nguồn duy nhất, nhưng chia thành hai build target, chỉ liên kết Sparkle trong một trong số đó. Series này đề cập đến phần thiết lập một lần để tạo target thứ hai đó (FocusTimer MAS).\nNhững Gì Series Này Xây Dựng Qua ba phần, bạn sẽ chuẩn bị được những thứ sau.\n(Phần 1, bài này) Đăng ký Bundle ID chỉ dành cho MAS + nhân đôi build target Xcode (Phần 2) Các file cấu hình (entitlements và Info.plist) và phân nhánh code phân tách hai kênh (Phần 3) ExportOptions-MAS.plist để upload + đăng ký App Store Connect + xác minh build Khi kết thúc series, bạn cần có:\nBundle ID chỉ dành cho MAS đã đăng ký trong Apple Developer Portal Build target FocusTimer MAS đã xóa dependency Sparkle File entitlements và Info.plist chỉ dành cho MAS Code được phân nhánh bằng #if canImport(Sparkle) ExportOptions-MAS.plist để upload lên App Store Ứng Dụng Mẫu — FocusTimer Như trong series trước, chúng ta sẽ dùng ứng dụng macOS giả tưởng FocusTimer (một ứng dụng đếm thời gian đơn giản để quản lý phiên tập trung) làm ví dụ. FocusTimer, bundle identifier com.example.FocusTimer, Team ID ABCDE12345, v.v. đều là giá trị mẫu. Trong thực tế, hãy thay thế bằng ứng dụng và thông tin tài khoản của bạn.\nBài viết này được viết dựa trên Xcode 26.\nBước 1 — Đăng Ký Bundle ID Chỉ Dành Cho MAS Tại Sao Cần Bundle ID Riêng Nếu Bundle ID của kênh phân phối trực tiếp là com.example.FocusTimer, kênh MAS sẽ dùng Bundle ID khác.\nKênh Bundle ID Developer ID com.example.FocusTimer Mac App Store com.example.FocusTimer.mas Bạn thêm .mas vào Bundle ID hiện có. Tại sao phải tách chúng?\nCả hai kênh có thể cùng tồn tại trên một Mac — Nếu Bundle ID giống nhau, macOS coi hai ứng dụng là cùng một ứng dụng và chúng sẽ xung đột. Nếu khác nhau, bạn có thể cài đặt bản phân phối trực tiếp và bản App Store trên cùng một máy cùng lúc (tiện lợi cho phát triển và kiểm thử). Phân tách domain UserDefaults — Bộ nhớ cài đặt được phân vùng theo Bundle ID, vì vậy cài đặt của hai kênh không bị trộn lẫn. Tránh xung đột Launch Services — Ngăn nhầm lẫn khi macOS quyết định ứng dụng nào để gửi yêu cầu như \u0026ldquo;mở ứng dụng này\u0026rdquo;. Giữ nguyên vẹn Bundle ID của kênh phân phối trực tiếp. Đó là định danh cho người dùng hiện tại của bạn, vì vậy không được thay đổi. Bạn đang đăng ký thêm một ID mới cho MAS.\nQuy Trình Đăng Ký Đăng nhập tại developer.apple.com/account (bằng tài khoản Apple Developer Program) Certificates, Identifiers \u0026amp; Profiles → Identifiers trong menu bên trái → nút + Chọn App IDs → Continue → chọn loại App → Continue Description: Nhập tên dễ nhận ra (ví dụ: FocusTimer MAS) Bundle ID: Chọn Explicit Nhập: com.example.FocusTimer.mas Capabilities: Tắt mọi tính năng ứng dụng không dùng. Nếu bạn không dùng iCloud, push notification, in-app purchase, Sign in with Apple, v.v., bạn không cần bật gì cả. (App Sandbox được xử lý bởi entitlements phía Xcode, vì vậy không cần bật ở đây.) Continue → Register Xác Minh Nếu danh sách Identifiers hiển thị mục FocusTimer MAS — com.example.FocusTimer.mas, đăng ký đã hoàn tất.\nBước 2 — Nhân Đôi Build Target Xcode Bây giờ bạn sẽ tạo build target FocusTimer MAS trong dự án Xcode. Cách nhanh nhất là nhân đôi target hiện có.\n2-1. Nhân Đôi Target Mở FocusTimer.xcodeproj trong Xcode Nhấp vào biểu tượng dự án (màu xanh) ở đầu Project navigator (bảng bên trái) Trong danh sách TARGETS ở vùng chỉnh sửa trung tâm, nhấp chuột phải vào FocusTimer → Duplicate Khi hộp thoại xuất hiện, chọn Duplicate Only Target mới được tạo với tên FocusTimer copy. Nhấp đúp vào tên và đổi thành FocusTimer MAS Nếu lời nhắc \u0026ldquo;Add a new Scheme?\u0026rdquo; xuất hiện, chọn Activate (hoặc thêm sau trong Manage Schemes) 2-2. Thay Đổi Bundle ID Ngay Lập Tức Điều đầu tiên cần làm ngay sau khi nhân đôi là thay Bundle ID. Nếu để nguyên, cả hai target sẽ có cùng ID.\nThay đổi TARGETS → FocusTimer MAS → General → Identity → Bundle Identifier thành giá trị bạn đã đăng ký ở Bước 1.\ncom.example.FocusTimer.mas 2-3. Bẫy Quan Trọng — \u0026ldquo;Gánh Nặng Dọn Dẹp\u0026rdquo; Mà Duplicate Để Lại Đây là phần phức tạp nhất của bài viết này. Duplicate Target của Xcode hoạt động với \u0026ldquo;giá trị mặc định an toàn\u0026rdquo;, nhưng chính sự an toàn đó để lại công việc dọn dẹp cần can thiệp thủ công. Ngay sau khi nhân đôi, bạn cần kiểm tra bốn điều sau.\n① Làm cho target mới bao gồm các file nguồn\nVì lý do an toàn, Duplicate tự động loại trừ tất cả file nguồn khỏi target mới. Nếu để nguyên, target FocusTimer MAS là một vỏ rỗng — build nó sẽ tạo ra build không có code gì. Bạn cần sửa lại các target membership để target mới bao gồm các file nguồn hiện có.\n② Xóa dependency Sparkle khỏi MAS target\nDuplicate sao chép nguyên văn các Swift Package dependency của bản gốc. Kết quả là Sparkle cũng theo vào target FocusTimer MAS. Toàn bộ điểm khởi đầu của series này là \u0026ldquo;MAS build không được có Sparkle\u0026rdquo;, vì vậy hãy xóa Sparkle khỏi danh sách package dependency của MAS target và khỏi Frameworks build phase.\n③ Dọn dẹp file Info.plist tạm thời\nDuplicate tạo ra file Info.plist tạm thời như FocusTimer copy-Info.plist. Vì chúng ta sẽ tạo Info.plist dành riêng cho MAS ở Phần 2, hãy xóa các file tạm thời này — cả project reference lẫn file thực tế.\n④ Cập nhật cài đặt build của MAS target\nBạn cần căn chỉnh các cài đặt build như bundle identifier, đường dẫn Info.plist và đường dẫn entitlements cho MAS. Chúng ta sẽ xử lý tất cả cùng một lúc ở Phần 2, sau khi tạo các file cấu hình chuyên dụng.\nMục ① và ② phần lớn có thể được xử lý từ các màn hình target membership và package dependency trong Xcode UI, nhưng nếu các tham chiếu trùng lặp còn lại từ quá trình nhân đôi không được dọn sạch gọn, đôi khi bạn cần nhìn trực tiếp vào file dự án (.pbxproj). Khi bạn nghĩ việc dọn dẹp đã xong, cách đáng tin cậy nhất để xác minh là build cả hai target riêng biệt và kiểm tra xem import Sparkle có bị lỗi trong MAS target build không (Phần 2 đề cập đến phân nhánh code này).\nTổng Kết Phần 1 Nếu bạn đã làm theo đến đây, bây giờ bạn có:\n✅ Bundle ID chỉ dành cho MAS (com.example.FocusTimer.mas) đã đăng ký trong Apple Developer Portal ✅ Build target FocusTimer MAS được tạo bằng cách nhân đôi ✅ Hiểu về gánh nặng dọn dẹp mà việc nhân đôi để lại (source membership, Sparkle dependency, file tạm thời) \u0026ldquo;Vỏ chứa\u0026rdquo; — target — đã được tạo. Nhưng target này về cơ bản vẫn chỉ là bản sao của FocusTimer; nó chưa thực sự trở thành \u0026ldquo;dành riêng cho MAS\u0026rdquo;. MAS build cần dùng entitlements khác và Info.plist khác so với build phân phối trực tiếp, và code cũng phải được phân nhánh để không bị lỗi khi thiếu Sparkle.\nỞ phần tiếp theo, chúng ta sẽ đề cập đến các file cấu hình và phân nhánh code phân tách hai kênh.\n","permalink":"https://hobbyworker.me/vi/dev/2026-05-17-distribute-macos-app-mas-1-target-setup/","summary":"\u003ch1 id=\"kênh-phân-phối-khác--mac-app-store\"\u003eKênh Phân Phối Khác — Mac App Store\u003c/h1\u003e\n\u003cp\u003eSeries trước đã đề cập đến phần thiết lập một lần để \u003cstrong\u003ephân phối trực tiếp ứng dụng macOS bằng Developer ID\u003c/strong\u003e. Khi bạn đã có chứng chỉ, công chứng, cập nhật tự động Sparkle và update feed được lưu trữ, bạn có thể cho người dùng tải file \u003ccode\u003e.dmg\u003c/code\u003e trực tiếp mà không cần qua App Store.\u003c/p\u003e\n\u003cp\u003eSeries này đề cập đến phần thiết lập một lần để đưa cùng ứng dụng đó lên \u003cstrong\u003eMac App Store (MAS)\u003c/strong\u003e. Hai phương thức phân phối không phải là sự lựa chọn một trong hai. Bạn có thể \u003cstrong\u003evận hành một ứng dụng qua cả kênh phân phối trực tiếp lẫn kênh App Store cùng một lúc\u003c/strong\u003e. App Store có lợi thế là Apple xử lý thanh toán, hoàn tiền và khả năng hiển thị tìm kiếm thay bạn, và nó mang lại độ tin cậy cao hơn từ người dùng, vì vậy việc chạy song song với phân phối trực tiếp là lựa chọn phổ biến.\u003c/p\u003e","title":"Phát Hành Ứng Dụng macOS Lên Mac App Store (1): Tạo Build Target Riêng Biệt"},{"content":"Mảnh Cuối Cùng — Đặt Bản Cập Nhật Ở Đâu Ở Phần 1 chúng ta đã chuẩn bị chứng chỉ Developer ID và công chứng, và ở Phần 2 chúng ta đã chuẩn bị khóa ký Sparkle. Nghĩa là chúng ta hiện có cách để ký ứng dụng, công chứng nó, và xác minh tính xác thực của bản cập nhật.\nNhưng vị trí được trỏ đến bởi SUFeedURL (https://updates.example.com/appcast.xml), mà chúng ta đã viết vào Info.plist của ứng dụng ở Phần 2, vẫn chưa có gì. Trong phần cuối này, chúng ta sẽ lưu trữ update feed cho vị trí đó và hoàn thiện cài đặt build, hoàn tất toàn bộ phần thiết lập một lần.\nNhư ở Phần 1 và 2, tất cả tên và domain (FocusTimer, example.com, example-dev, v.v.) đều là giá trị mẫu. Trong thực tế, hãy thay thế bằng thông tin của bạn.\nTại Sao Cần Repository Cập Nhật Riêng Để cập nhật tự động hoạt động, hai thứ phải có trên internet.\nappcast.xml — update feed cho biết ứng dụng phiên bản nào là mới nhất và lấy ở đâu .dmg — file cài đặt ứng dụng thực tế Có một ràng buộc quan trọng ở đây. Code Sparkle bên trong ứng dụng tải các file này bằng HTTPS GET đơn giản, không xác thực. Điều này có nghĩa là ứng dụng trên máy tính của người dùng phải có thể tải chúng trực tiếp, không cần bất kỳ thủ tục nào như đăng nhập.\nNhiều nhà phát triển giữ repository nguồn chính của ứng dụng ở chế độ riêng tư (private). Nhưng các file phát hành trong repository riêng tư yêu cầu xác thực, vì vậy Sparkle không thể tải chúng. Đó là lý do tại sao cấu trúc phổ biến là tách repository.\nRepository chính (ví dụ: FocusTimer) — mã nguồn. Có thể để riêng tư. Repository cập nhật (ví dụ: FocusTimer-updates) — chỉ lưu trữ appcast.xml. Phải là công khai (public). Trong bài viết này, chúng ta sẽ chạy repository cập nhật trên GitHub Pages, dịch vụ lưu trữ tĩnh miễn phí của GitHub.\nBước 1 — Tạo Repository Cập Nhật Công Khai Tạo repository mới trên GitHub.\nNhấp New repository Tên: FocusTimer-updates — tên này được dùng trong địa chỉ feed ngay sau đây, vì vậy hãy đặt chính xác, kể cả chữ hoa chữ thường. Owner: tài khoản hoặc tổ chức của bạn (ví dụ: example-dev) Visibility: Public — bắt buộc, vì Sparkle phải tải nó mà không cần xác thực. Đánh dấu Add a README file (để có commit đầu tiên thuận tiện) Nhấp Create repository Bước 2 — Kích Hoạt GitHub Pages Phục vụ repository bạn vừa tạo như một site tĩnh.\nVào Settings của repository → Pages trong menu bên trái Source: Deploy from a branch Branch: chọn main / (root) → Save Sau 1–2 phút, nếu https://example-dev.github.io/FocusTimer-updates/ có thể truy cập được, nghĩa là thành công. Tại điểm này bạn đã có địa chỉ công khai để đặt file cập nhật. Nhưng còn một bước nữa.\nBước 3 — Kết Nối Domain Tùy Chỉnh Bạn có thể dùng địa chỉ GitHub Pages mặc định (example-dev.github.io/...) như vậy và nó sẽ hoạt động. Nhưng nếu bạn nhúng địa chỉ đó vào SUFeedURL của ứng dụng, bạn sẽ gặp rắc rối nếu sau này cần chuyển hosting từ GitHub Pages sang nơi khác — vì ứng dụng của mọi người dùng đã được phân phối vẫn trỏ đến địa chỉ cũ.\nGiải pháp là chèn vào một domain bạn kiểm soát. Nếu bạn đặt SUFeedURL thành domain của mình, như https://updates.example.com/appcast.xml, thì khi bạn chuyển hosting sau này, bạn chỉ cần thay đổi một dòng cấu hình DNS và người dùng hiện tại tự động theo đến vị trí mới. Bạn chỉ cần thiết lập này một lần, và nó có hiệu lực mãi mãi.\n3-1. Thêm Bản Ghi DNS Trong màn hình cấu hình của nhà cung cấp DNS quản lý domain của bạn (example.com), thêm bản ghi sau.\nTrường Giá trị Type CNAME Name updates Value example-dev.github.io TTL 3600 (mặc định) Điều này làm cho subdomain updates.example.com trỏ đến GitHub Pages.\n3-2. Thêm File CNAME Vào Repository Trong thư mục gốc của repository cập nhật (FocusTimer-updates), tạo file có tên CNAME với nội dung chỉ là một dòng — tên domain.\nupdates.example.com Commit và push file này.\n3-3. Đăng Ký Domain Với GitHub Pages Trong trường Settings → Pages → Custom domain của repository, nhập updates.example.com và nhấp Save. GitHub tự động cấp chứng chỉ HTTPS, và sau khi cấp xong, hãy đánh dấu Enforce HTTPS.\n3-4. Xác Nhận Truyền bá DNS và cấp chứng chỉ thường mất khoảng 10 phút. Sau khi chờ một chút, hãy xác nhận bằng lệnh sau.\ncurl -I https://updates.example.com/appcast.xml Nếu dòng đầu tiên của phản hồi hiển thị HTTP/2 200, mọi thứ đều ổn. (Nếu bạn chưa tải appcast.xml lên, bạn có thể nhận 404, nhưng kết nối domain và HTTPS có thể được xác nhận qua các đường dẫn khác. Điểm mấu chốt là https://updates.example.com trả về phản hồi.)\nĐể tham khảo, file cài đặt .dmg thường được tải lên GitHub Releases chứ không phải GitHub Pages. Đặt các file nhị phân lớn trực tiếp vào repository sẽ làm nó cồng kềnh. Releases được phục vụ qua đường dẫn khác với Pages, vì vậy hãy để nguyên như vậy, không liên quan đến cài đặt domain tùy chỉnh ở trên.\nBước 4 — Giữ Repository Cập Nhật Ở Cục Bộ Để chỉnh sửa và commit appcast.xml trong quá trình phát hành, bạn cần có repository cập nhật được checkout ở cục bộ. Nếu bạn clone nó đến vị trí thích hợp trong thư mục dự án chính (ví dụ: release/updates), các script build và phát hành có thể xử lý cả hai repository từ một nơi, điều này rất tiện lợi.\ngit clone git@github.com:example-dev/FocusTimer-updates.git release/updates Khi bạn giữ một repository bên trong repository khác như thế này, hãy thêm release/updates/ vào .gitignore của repository chính để chúng không can thiệp lẫn nhau.\nBước 5 — ExportOptions.plist Bây giờ là cài đặt phía build. Lệnh xcodebuild -exportArchive của Xcode đọc file cấu hình có tên ExportOptions.plist khi xuất archive thành .app để phân phối. Nội dung dành cho phân phối trực tiếp (kênh Developer ID) như sau.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;method\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;developer-id\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;signingStyle\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;automatic\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;teamID\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;ABCDE12345\u0026lt;/string\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; method — developer-id. Có nghĩa là \u0026ldquo;phân phối trực tiếp, không phải Mac App Store\u0026rdquo;. signingStyle — automatic. Để Xcode tự động chọn và dùng chứng chỉ được cấp ở Phần 1. teamID — Team ID bạn đã ghi lại ở Phần 1. Tạo file này một lần trong dự án (ví dụ: release/ExportOptions.plist) và tái sử dụng cho mọi lần phát hành.\nBước 6 — Kiểm Tra Cài Đặt Phía Ứng Dụng Cuối cùng, hãy xem lại các cài đặt phải có trong dự án ứng dụng. Nếu bạn đang thêm Sparkle vào ứng dụng mới lần đầu, bạn có thể dùng danh sách này như một checklist.\nInfo.plist Đây là các khóa Sparkle được thêm ở Phần 2. Info.plist phải bao gồm các khóa sau.\n\u0026lt;key\u0026gt;SUFeedURL\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;https://updates.example.com/appcast.xml\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;SUPublicEDKey\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=\u0026lt;/string\u0026gt; Kiểm tra kỹ rằng SUFeedURL trỏ đến domain tùy chỉnh được tạo ở Bước 3, và SUPublicEDKey khớp với khóa công khai được tạo ở Phần 2.\nQuyền Hạn (Entitlements) Nếu ứng dụng sử dụng App Sandbox, Sparkle cần các quyền ngoại lệ để có thể giao tiếp với các dịch vụ nội bộ để cài đặt bản cập nhật. Các mục sau được đưa vào file FocusTimer.entitlements.\n\u0026lt;key\u0026gt;com.apple.security.app-sandbox\u0026lt;/key\u0026gt;\u0026lt;true/\u0026gt; \u0026lt;key\u0026gt;com.apple.security.network.client\u0026lt;/key\u0026gt;\u0026lt;true/\u0026gt; \u0026lt;key\u0026gt;com.apple.security.temporary-exception.mach-lookup.global-name\u0026lt;/key\u0026gt; \u0026lt;array\u0026gt; \u0026lt;string\u0026gt;$(PRODUCT_BUNDLE_IDENTIFIER)-spks\u0026lt;/string\u0026gt; \u0026lt;string\u0026gt;$(PRODUCT_BUNDLE_IDENTIFIER)-spki\u0026lt;/string\u0026gt; \u0026lt;/array\u0026gt; Tại thời điểm build, Xcode tự động thay thế $(PRODUCT_BUNDLE_IDENTIFIER) bằng bundle identifier thực tế (com.example.FocusTimer). Để biết chi tiết về từng mục, hãy xem hướng dẫn sandboxing chính thức của Sparkle.\nSandboxing không bắt buộc đối với ứng dụng phân phối trực tiếp (sandbox là yêu cầu của Mac App Store). Nếu ứng dụng của bạn không dùng sandbox, không cần ngoại lệ mach-lookup ở trên. Tuy nhiên, vì cập nhật tự động dùng mạng, quyền hạn network.client và Hardened Runtime phải được bật để công chứng.\nCài Đặt Build (Build Settings) Trong cài đặt build của Xcode, hãy kiểm tra những điều sau.\nCODE_SIGN_ENTITLEMENTS — đường dẫn đến file entitlements ở trên ENABLE_HARDENED_RUNTIME = YES — điều kiện bắt buộc để công chứng ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES — cho phép truy cập mạng để kiểm tra cập nhật Tổng Kết Series — Hoàn Tất Thiết Lập Một Lần Phần thiết lập một lần cho phân phối trực tiếp qua ba phần hiện đã hoàn tất. Bạn hiện có:\n✅ (Phần 1) Chứng chỉ Developer ID Application + thông tin xác thực để công chứng ✅ (Phần 2) Cặp khóa ký EdDSA Sparkle + sao lưu khóa riêng tư ✅ (Phần 3) Repository cập nhật công khai được kết nối với domain tùy chỉnh + ExportOptions.plist + cấu hình phía ứng dụng Đây là tất cả những gì chỉ cần làm một lần. Bạn sẽ không cần làm lại công việc này mỗi lần phát hành phiên bản mới.\nTừ đây trở đi, luồng phân phối phiên bản mới lặp lại theo cách tương tự mỗi lần — build archive → xuất với ExportOptions.plist → ký bằng chứng chỉ Developer ID → công chứng bằng notarytool → tạo .dmg → ký bằng khóa Sparkle → cập nhật appcast.xml → tải .dmg lên GitHub Release. Quy trình lặp đi lặp lại này có thể được tự động hóa phần lớn bằng một script duy nhất, và đó là chủ đề cho một bài viết khác.\nTrong số đó, tạo .dmg liên quan đến các yếu tố thiết kế như hình nền và vị trí icon, vì vậy nó được đề cập chi tiết trong series riêng Thiết Kế DMG Phân Phối Cho Ứng Dụng macOS Của Bạn.\nPhân phối trực tiếp trông có vẻ đáng sợ lúc đầu vì có nhiều thứ cần thiết lập, nhưng điểm mấu chốt là \u0026ldquo;một khi thiết lập xong, nó cứ được tái sử dụng tiếp tục.\u0026rdquo; Đổi lại việc từ bỏ một phần sự tiện lợi của App Store, bạn kiểm soát được mọi phần của quy trình phân phối.\nTài Liệu Tham Khảo Tài liệu chính thức Sparkle Apple — Notarizing macOS software before distribution Tài liệu GitHub Pages ","permalink":"https://hobbyworker.me/vi/dev/2026-05-16-distribute-macos-app-3-update-hosting-and-build/","summary":"\u003ch1 id=\"mảnh-cuối-cùng--đặt-bản-cập-nhật-ở-đâu\"\u003eMảnh Cuối Cùng — Đặt Bản Cập Nhật Ở Đâu\u003c/h1\u003e\n\u003cp\u003eỞ \u003ca href=\"../2026-05-14-distribute-macos-app-1-developer-id-certificate/\"\u003ePhần 1\u003c/a\u003e chúng ta đã chuẩn bị chứng chỉ Developer ID và công chứng, và ở \u003ca href=\"../2026-05-15-distribute-macos-app-2-sparkle-signing-key/\"\u003ePhần 2\u003c/a\u003e chúng ta đã chuẩn bị khóa ký Sparkle. Nghĩa là chúng ta hiện có cách để ký ứng dụng, công chứng nó, và xác minh tính xác thực của bản cập nhật.\u003c/p\u003e\n\u003cp\u003eNhưng vị trí được trỏ đến bởi \u003ccode\u003eSUFeedURL\u003c/code\u003e (\u003ccode\u003ehttps://updates.example.com/appcast.xml\u003c/code\u003e), mà chúng ta đã viết vào \u003ccode\u003eInfo.plist\u003c/code\u003e của ứng dụng ở Phần 2, vẫn chưa có gì. Trong phần cuối này, chúng ta sẽ \u003cstrong\u003elưu trữ update feed\u003c/strong\u003e cho vị trí đó và hoàn thiện \u003cstrong\u003ecài đặt build\u003c/strong\u003e, hoàn tất toàn bộ phần thiết lập một lần.\u003c/p\u003e","title":"Tự Phân Phối Ứng Dụng macOS (3): Lưu Trữ Update Feed và Cài Đặt Build"},{"content":"Cập Nhật Tự Động, và Lý Do Cần Thêm Một Lớp Ký Nữa Ở Phần 1, chúng ta đã hoàn thành việc thiết lập chứng chỉ Developer ID và công chứng. Với đó, bạn đã sẵn sàng giao ứng dụng cho người dùng lần đầu. Nhưng ứng dụng không kết thúc sau một lần phát hành — bạn phải tiếp tục phát hành các phiên bản mới sửa lỗi và thêm tính năng.\nVới ứng dụng Mac App Store, App Store xử lý cập nhật cho bạn. Ứng dụng phân phối trực tiếp không được như vậy, vì vậy bạn phải tự xây dựng tính năng cập nhật tự động vào ứng dụng. Trên macOS, tiêu chuẩn thực tế cho vai trò này là framework mã nguồn mở Sparkle. Với Sparkle, ứng dụng định kỳ kiểm tra \u0026ldquo;update feed (appcast)\u0026rdquo;, và nếu có phiên bản mới, nó thông báo cho người dùng, tải xuống và cài đặt.\nĐiều này đặt ra một câu hỏi. Bạn đã ký ứng dụng bằng chứng chỉ Developer ID được tạo ở Phần 1, vậy tại sao lại cần thêm một khóa khác?\nLý do là hai chữ ký xác minh những thứ khác nhau.\nChứng chỉ Developer ID — được macOS Gatekeeper dùng để quyết định \u0026ldquo;có ổn không khi cài đặt ứng dụng này?\u0026rdquo; Khóa EdDSA của Sparkle — được Sparkle bên trong ứng dụng dùng để quyết định \u0026ldquo;file cập nhật tôi vừa tải xuống có thực sự được tạo bởi nhà phát triển ứng dụng này không?\u0026rdquo; Cập nhật tự động là thao tác nhạy cảm về bảo mật: ứng dụng tải file từ internet và ghi đè lên chính nó. Nếu ai đó chặn máy chủ cập nhật hoặc đường truyền và đưa vào file giả, đó là vấn đề nghiêm trọng. Để ngăn điều này, Sparkle chỉ chấp nhận các bản cập nhật được ký bằng khóa riêng tư mà chỉ nhà phát triển nắm giữ, và từ chối cài đặt bất kỳ thứ gì không khớp chữ ký. Đây thực chất là một lớp xác minh riêng biệt so với chứng chỉ.\nTrong bài viết này, chúng ta sẽ tạo cặp khóa EdDSA (Ed25519) sẽ được dùng cho việc xác minh đó.\nNhư ở Phần 1, tất cả tên và đường dẫn (FocusTimer, example.com, v.v.) đều là giá trị mẫu. Trong thực tế, hãy thay thế bằng thông tin ứng dụng của bạn.\nĐiều Kiện Tiên Quyết — Sparkle Phải Đã Được Thêm Vào Ứng Dụng Trước khi tạo khóa, framework Sparkle phải đã được thêm vào dự án ứng dụng của bạn như một dependency. Nếu chưa, hãy thêm vào trong Xcode qua Swift Package Manager (SPM).\nMở dự án trong Xcode → File → Add Package Dependencies… Nhập địa chỉ repository vào ô tìm kiếm: https://github.com/sparkle-project/Sparkle Đặt quy tắc phiên bản thành 2.x (major mới nhất) và thêm vào Sau khi làm điều này, build dự án một lần để SPM tải gói Sparkle xuống. Các công cụ dòng lệnh đi kèm với nó là chìa khóa cho bước tiếp theo.\nBước 1 — Tìm Các Công Cụ Dòng Lệnh Của Sparkle Gói Sparkle bao gồm các công cụ dòng lệnh dùng để tạo và ký khóa. Các công cụ này nằm trong thư mục mà SPM đã tải gói xuống, nhưng vị trí đó thay đổi tùy theo phiên bản Xcode và cài đặt DerivedData. Vì vậy, an toàn hơn là tìm trực tiếp.\nSPARKLE_BIN=$(find ~/Library/Developer/Xcode/DerivedData \\ -path \u0026#34;*/artifacts/sparkle/Sparkle/bin\u0026#34; -type d 2\u0026gt;/dev/null | head -1) echo \u0026#34;$SPARKLE_BIN\u0026#34; Nếu một đường dẫn được in ra, nghĩa là thành công. Nếu không có gì xuất hiện, bạn đã bỏ qua bước \u0026ldquo;build dự án một lần\u0026rdquo; ở trên — hãy chạy build trong Xcode rồi thử lại.\nBên trong thư mục này có các công cụ sau.\ngenerate_keys — tạo, sao lưu, khôi phục và xác minh khóa ký (được dùng trong bài viết này) sign_update — ký file cập nhật (được dùng trong các lần phát hành thực tế) generate_appcast — tạo update feed (appcast.xml) (xuất hiện ở Phần 3) Bước 2 — Tạo Khóa Ký Bây giờ hãy tạo cặp khóa.\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; Đầu ra tương tự như sau sẽ xuất hiện.\nGenerating a new signing key... A key has been generated and saved in your keychain. Add the SUPublicEDKey key to the Info.plist of each app for which you intend to use Sparkle... \u0026lt;key\u0026gt;SUPublicEDKey\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=\u0026lt;/string\u0026gt; Lệnh duy nhất này tạo ra hai khóa.\nKhóa công khai (public key) — giá trị SUPublicEDKey được hiển thị trong đầu ra ở trên. Đây không phải bí mật, và là khóa bạn sẽ nhúng vào ứng dụng. Khóa riêng tư (private key) — không xuất hiện trên màn hình. Nó được tự động lưu trong Keychain của macOS dưới dạng mục \u0026ldquo;Private key for signing Sparkle updates\u0026rdquo;. Đây là bí mật thực sự không bao giờ được để lại trên đĩa dưới dạng file văn bản thuần túy. Nhúng Khóa Công Khai Vào Ứng Dụng Đặt chuỗi khóa công khai từ đầu ra vào Info.plist của ứng dụng. Với ứng dụng mẫu, hãy thêm các khóa sau vào FocusTimer-Info.plist.\n\u0026lt;key\u0026gt;SUFeedURL\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;https://updates.example.com/appcast.xml\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;SUPublicEDKey\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=\u0026lt;/string\u0026gt; SUPublicEDKey — khóa công khai bạn vừa tạo. Ứng dụng dùng khóa này để xác minh chữ ký của các bản cập nhật đã tải xuống. SUFeedURL — địa chỉ của update feed. Domain này chưa tồn tại; chúng ta tạo ở Phần 3. Bây giờ chỉ là chỗ giữ vị trí. Vì khóa công khai được nhúng vào ứng dụng và chỉ nhà phát triển mới giữ khóa riêng tư, ứng dụng sẽ chỉ chấp nhận các bản cập nhật được ký bằng khóa riêng tư. Đây là cấu trúc cốt lõi của xác minh cập nhật Sparkle.\nBước 3 — Sao Lưu Khóa Riêng Tư (Bắt Buộc!) Nếu bạn bỏ qua bước này, bạn có thể hối tiếc sâu sắc về sau.\nKhóa riêng tư được lưu trong Keychain, vì vậy trên máy tính bạn đang dùng hiện tại thì không sao. Nhưng nếu bạn mất máy tính, đĩa bị hỏng, hoặc cài lại macOS, khóa này cũng biến mất theo.\nĐiều gì xảy ra nếu khóa riêng tư bị mất? Các bản cập nhật được ký bằng khóa mới sẽ bị ứng dụng của người dùng hiện tại từ chối (ứng dụng có khóa công khai cũ được nhúng). Nói cách khác, bạn không bao giờ có thể gửi cập nhật tự động đến những người dùng đang chạy ứng dụng của bạn nữa. Lựa chọn duy nhất của bạn là nói với từng người dùng \u0026ldquo;vui lòng tự tải phiên bản mới và cài đặt lại\u0026rdquo;.\nVì vậy hãy sao lưu khóa ngay sau khi tạo.\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -x ~/focustimer-sparkle-private.key cat ~/focustimer-sparkle-private.key Chuỗi base64 một dòng được cat in ra là khóa riêng tư. Ví dụ:\nHn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0= Lưu chuỗi này dưới dạng ghi chú bảo mật (secure note) trong trình quản lý mật khẩu như 1Password. Đặt tên ghi chú dễ tìm về sau, chẳng hạn như FocusTimer Sparkle EdDSA Private Key.\nNgay sau khi xác nhận đã lưu, hãy xóa file văn bản thuần túy còn lại trên đĩa.\nrm ~/focustimer-sparkle-private.key Quy tắc là không bao giờ để khóa riêng tư nằm trên đĩa dưới dạng file văn bản thuần túy. Chỉ giữ bản sao lưu bên trong trình quản lý mật khẩu được mã hóa.\nBẫy Quan Trọng — Ký Tự % Không Phải Là Một Phần Của Khóa Khi bạn in khóa bằng cat, terminal (đặc biệt là zsh) có thể thêm ký tự % vào cuối dòng.\nHn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0=% % này chỉ là chỉ báo của shell rằng \u0026ldquo;đầu ra kết thúc mà không có dòng mới\u0026rdquo; — nó không phải là một phần của khóa. Nếu bạn sao chép % này vào bản sao lưu, khóa sẽ bị hỏng khi bạn khôi phục sau này. Chuỗi base64 thường kết thúc bằng =, vì vậy hãy loại trừ % sau = khi lưu.\nBước 4 — Khôi Phục Trên Máy Tính Khác Khi bạn cần build ứng dụng trên máy tính mới, hãy đưa khóa riêng tư đã sao lưu trở lại Keychain.\necho \u0026#34;chuỗi_base64_đã_sao_lưu_của_bạn\u0026#34; \u0026gt; ~/focustimer-sparkle-private.key \u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -f ~/focustimer-sparkle-private.key rm ~/focustimer-sparkle-private.key Tùy chọn -f có nghĩa là \u0026ldquo;nhập khóa trong file vào Keychain\u0026rdquo;. Sau khi khôi phục xong, hãy xóa file văn bản thuần túy ngay ở đây cũng vậy.\nBước 5 — Xác Nhận Kiểm tra xem khóa đã được cài đặt đúng chưa.\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -p Một dòng chứa khóa công khai được in ra. Giá trị này phải khớp chính xác với giá trị bạn đã đặt vào SUPublicEDKey trong Info.plist ở Bước 2. Nếu khác nhau, khóa công khai được nhúng vào ứng dụng và khóa ký thực tế không đồng bộ, và xác minh cập nhật sẽ thất bại.\nTổng Kết Phần 2 Nếu bạn đã làm theo đến đây, bây giờ bạn có:\n✅ Cặp khóa EdDSA Sparkle được tạo (khóa công khai + khóa riêng tư) ✅ Khóa công khai được nhúng vào Info.plist của ứng dụng (SUPublicEDKey) ✅ Khóa riêng tư được sao lưu an toàn trong trình quản lý mật khẩu ✅ Hiểu cách khôi phục trên máy tính khác Ứng dụng hiện có cách để xác minh \u0026ldquo;liệu bản cập nhật đã tải xuống có thực sự là bản gốc không\u0026rdquo;. Nhưng có một thứ vẫn còn thiếu. Ở Phần 2 bạn đã viết https://updates.example.com/appcast.xml cho SUFeedURL, nhưng địa chỉ đó vẫn chưa có gì.\nỞ phần tiếp theo, chúng ta sẽ tạo repository công khai nơi update feed (appcast.xml) và file .dmg sẽ được lưu, kết nối nó với domain chúng ta kiểm soát, và hoàn thiện cài đặt build — kết thúc phần thiết lập một lần.\n","permalink":"https://hobbyworker.me/vi/dev/2026-05-15-distribute-macos-app-2-sparkle-signing-key/","summary":"\u003ch1 id=\"cập-nhật-tự-động-và-lý-do-cần-thêm-một-lớp-ký-nữa\"\u003eCập Nhật Tự Động, và Lý Do Cần Thêm Một Lớp Ký Nữa\u003c/h1\u003e\n\u003cp\u003eỞ \u003ca href=\"../2026-05-14-distribute-macos-app-1-developer-id-certificate/\"\u003ePhần 1\u003c/a\u003e, chúng ta đã hoàn thành việc thiết lập chứng chỉ Developer ID và công chứng. Với đó, bạn đã sẵn sàng giao ứng dụng cho người dùng lần đầu. Nhưng ứng dụng không kết thúc sau một lần phát hành — bạn phải tiếp tục phát hành các phiên bản mới sửa lỗi và thêm tính năng.\u003c/p\u003e","title":"Tự Phân Phối Ứng Dụng macOS (2): Tạo Khóa Ký Cập Nhật Tự Động Sparkle"},{"content":"Phân Phối Trực Tiếp Bằng Developer ID Nghĩa Là Gì Có hai cách chính để đưa ứng dụng macOS đến tay người dùng. Một là thông qua Mac App Store (MAS), hai là phân phối trực tiếp (direct distribution) — cho người dùng tải về file .dmg (hoặc .app) mà bạn tự tạo từ website, GitHub hay nơi tương tự.\nPhân phối trực tiếp có những lợi thế rõ ràng. Bạn không cần chờ App Store phê duyệt, không mất phí hoa hồng, và có thể phát hành bản cập nhật bất cứ lúc nào theo cách mình muốn. Đổi lại, những việc mà App Store từng làm thay — ký code, công chứng (notarization), và cập nhật tự động — giờ bạn phải tự lo liệu.\nNếu không chuẩn bị những điều này, cơ chế bảo mật Gatekeeper của macOS sẽ ngăn ứng dụng khởi chạy. Khi người dùng mở ứng dụng lần đầu, họ sẽ thấy cảnh báo kiểu \u0026ldquo;không thể mở ứng dụng này vì nó đến từ nhà phát triển chưa được xác định\u0026rdquo;, và hầu hết người dùng sẽ bỏ cuộc ngay tại đó. Để ứng dụng mở bằng cú đúp đơn giản như bất kỳ ứng dụng được phân phối đúng cách nào, bạn cần ký ứng dụng bằng chứng chỉ Developer ID và được Apple công chứng.\nSeries này đề cập đến quá trình cài đặt ban đầu đó. Một tin vui là hầu hết các thiết lập ở đây đều chỉ cần làm một lần và tái sử dụng cho mọi lần phát hành.\nNhững Gì Bạn Sẽ Xây Dựng Trong Series Này Qua ba phần, bạn sẽ chuẩn bị được những thứ sau.\n(Phần 1, bài này) Chứng chỉ Developer ID Application + thông tin xác thực để công chứng (Phần 2) Khóa ký EdDSA cho cập nhật tự động Sparkle (Phần 3) Repository công khai để lưu trữ update feed + hoàn thiện cài đặt build Khi kết thúc series, bạn cần có sẵn những thứ sau đây. Bây giờ hãy lướt qua danh sách; mỗi phần sẽ điền vào từng mục một.\nChứng chỉ Developer ID Application trong macOS Keychain Hồ sơ thông tin xác thực công chứng được lưu trong Keychain Cặp khóa EdDSA để ký bản cập nhật Sparkle (khóa công khai + khóa riêng tư) Sao lưu khóa riêng tư ở nơi an toàn Repository GitHub công khai + GitHub Pages để lưu trữ update feed (appcast) File cài đặt build (ExportOptions.plist) và cấu hình phía ứng dụng Ứng Dụng Mẫu — FocusTimer Series này sử dụng ứng dụng macOS giả tưởng FocusTimer (một ứng dụng đếm thời gian đơn giản để quản lý thời gian tập trung) làm ví dụ xuyên suốt. Tên FocusTimer, bundle identifier com.example.FocusTimer, domain example.com, tên chứng chỉ, Team ID, v.v. xuất hiện trong các lệnh và đường dẫn đều là giá trị mẫu. Khi thực hành, hãy thay thế bằng tên ứng dụng và thông tin tài khoản của bạn.\nBài viết này được viết dựa trên Xcode 26 và Sparkle 2.x (framework cập nhật tự động, xuất hiện ở Phần 2). Apple thường thay đổi bố cục màn hình và vị trí menu, vì vậy dù tên nút có hơi khác, hãy hiểu rằng luồng tổng thể vẫn giống nhau.\nĐiều Kiện Tiên Quyết — Công Cụ Dòng Lệnh Trước tiên, hãy cài đặt hai công cụ dòng lệnh cần thiết cho tự động hóa build và phát hành. Phần này giả sử bạn đã cài trình quản lý gói macOS Homebrew.\nbrew install gh create-dmg gh — CLI chính thức của GitHub. Bạn sẽ dùng nó sau này để tạo GitHub Releases. create-dmg — Công cụ tạo file ảnh đĩa .dmg để phân phối. Nó cũng tạo ra màn hình hướng dẫn người dùng kéo ứng dụng vào thư mục Applications. Bạn chưa dùng ngay bây giờ, nhưng script tự động hóa phát hành thường được thiết kế để kiểm tra sự tồn tại của các công cụ này trước và dừng ngay nếu thiếu, vì vậy hãy cài trước.\nBước 1 — Xác Nhận Tư Cách Thành Viên Apple Developer Program Để cấp chứng chỉ Developer ID và công chứng ứng dụng, bạn cần tư cách thành viên Apple Developer Program. Với tài khoản cá nhân, đây là chương trình có phí, $99 mỗi năm.\nNếu bạn đã đăng ký, chỉ cần xác nhận còn hiệu lực.\nTruy cập developer.apple.com/account Trong phần Membership details, xác nhận trạng thái là Active Trên cùng màn hình đó, ghi lại Team ID của bạn (trong ví dụ là ABCDE12345). Nó được dùng suốt các bước sau. Nếu bạn chưa đăng ký, việc phê duyệt tư cách thành viên thường mất khoảng một ngày. Không thể cấp chứng chỉ trước khi được phê duyệt, vì vậy tốt nhất hãy xử lý việc này trước tiên.\nBước 2 — Cấp Chứng Chỉ Developer ID Application Chứng chỉ Developer ID Application là chứng chỉ dùng để ký .app và .dmg bạn phân phối. macOS Gatekeeper nhận ra ứng dụng được ký bằng chứng chỉ này là \u0026ldquo;ứng dụng được tạo bởi nhà phát triển đã biết\u0026rdquo;.\nApple cũng có chứng chỉ Developer ID Installer riêng để ký các gói cài đặt .pkg. Vì series này phân phối qua .dmg, chúng ta chỉ đề cập đến chứng chỉ Application.\nQuy Trình Cấp Cách đơn giản nhất là thông qua Xcode.\nMở Xcode → menu Xcode → Settings… (⌘,) Chọn tab Accounts → nhấp vào Apple ID của bạn trong danh sách bên trái Nhấp nút Manage Certificates… ở dưới cùng bên phải Trong cửa sổ mới mở ra, nhấp nút + ở dưới cùng bên trái Chọn Developer ID Application từ menu Sau một hoặc hai giây, chứng chỉ mới xuất hiện trong danh sách — nhấp Done Chứng chỉ được cấp theo cách này sẽ tự động được lưu vào Keychain của macOS. Chứng chỉ và khóa riêng tư tương ứng được lưu cùng nhau thành một cặp, và khóa riêng tư này mới là thứ cho phép ký.\nXác Nhận Việc Cấp Trong terminal, chạy lệnh sau để xác nhận chứng chỉ đã được cài đặt đúng.\nsecurity find-identity -v -p codesigning | grep \u0026#34;Developer ID Application\u0026#34; Nếu một dòng như sau xuất hiện, nghĩa là thành công.\n1) A1B2C3D4E5F6... \u0026#34;Developer ID Application: Gildong Hong (ABCDE12345)\u0026#34; Chuỗi ký tự trong dấu ngoặc kép là tên chính thức của chứng chỉ. Gildong Hong là tên đã đăng ký trên tài khoản Apple, và ABCDE12345 trong ngoặc đơn là Team ID bạn đã ghi lại ở Bước 1. Tên này được dùng nguyên vẹn khi tự động hóa ký code sau này, vì vậy hãy kiểm tra kỹ một lần nữa.\nGia Hạn Chứng Chỉ Chứng chỉ Developer ID có hiệu lực 5 năm. Khi gần hết hạn, nó sẽ được đánh dấu Expired trong danh sách Manage Certificates của Xcode. Khi đó, chỉ cần cấp chứng chỉ mới theo đúng quy trình như trên.\nTin vui là các ứng dụng đã được ký và phân phối bằng chứng chỉ cũ không bị vô hiệu hóa ngay khi nó hết hạn. Chỉ những build mới tạo ra từ đây trở đi mới cần ký bằng chứng chỉ mới. Vì vậy không cần lo lắng quá nhiều về việc hết hạn.\nBước 3 — Đăng Ký Mật Khẩu Dành Riêng Cho App Để Công Chứng Công Chứng (notarization) Là Gì? Công chứng là quy trình tải ứng dụng lên máy chủ Apple trước khi phân phối để quét phần mềm độc hại. Nếu vượt qua quét, Apple cấp \u0026ldquo;vé công chứng\u0026rdquo;, và vé đó phải được đính kèm vào ứng dụng để Gatekeeper mở ứng dụng mà không có cảnh báo. Nếu việc ký chứng minh \u0026ldquo;ai đã tạo ra nó\u0026rdquo;, thì công chứng là một quy trình riêng biệt chứng minh \u0026ldquo;Apple đã quét nó một lần\u0026rdquo;.\nViệc nộp công chứng sử dụng lệnh notarytool của Apple, lệnh này yêu cầu mật khẩu Apple ID mỗi lần bạn nộp. Để tránh nhập mỗi lần, bạn tạo mật khẩu dành riêng cho app (app-specific password) và lưu vào Keychain trước.\n3-1. Cấp Mật Khẩu Dành Riêng Cho App Mật khẩu dành riêng cho app là mật khẩu 16 ký tự chỉ dùng cho một mục đích cụ thể, thay cho mật khẩu Apple ID chính của bạn.\nTruy cập appleid.apple.com → đăng nhập bằng Apple ID đã đăng ký Apple Developer Program (trong ví dụ là developer@example.com) Vào Sign-In and Security → chọn App-Specific Passwords Nhấp nút + → nhập tên cho mật khẩu (ví dụ: focustimer-notarize) Nhấp Create → nhập mật khẩu Apple ID của bạn một lần nữa Mật khẩu 16 ký tự có dạng abcd-efgh-ijkl-mnop xuất hiện trên màn hình. Khi bạn đóng màn hình này, bạn không thể xem lại mật khẩu nữa. Trước khi chuyển sang bước tiếp theo, hãy sao chép tạm vào trình quản lý mật khẩu hoặc nơi tương tự.\n3-2. Lưu Vào Hồ Sơ notarytool Lưu mật khẩu bạn nhận được dưới dạng hồ sơ Keychain. Trong lệnh bên dưới, hãy thay giá trị --password bằng mật khẩu thực tế bạn vừa nhận trước khi chạy.\nxcrun notarytool store-credentials \u0026#34;focustimer-notarize\u0026#34; \\ --apple-id \u0026#34;developer@example.com\u0026#34; \\ --team-id \u0026#34;ABCDE12345\u0026#34; \\ --password \u0026#34;abcd-efgh-ijkl-mnop\u0026#34; Đối số đầu tiên \u0026quot;focustimer-notarize\u0026quot; — tên đặt cho hồ sơ này. Từ nay, bạn sẽ tải thông tin xác thực bằng tên này khi công chứng. --apple-id — email của tài khoản Apple Developer Program --team-id — Team ID từ Bước 1 --password — mật khẩu dành riêng cho app được cấp ở 3-1 Nếu xuất hiện đầu ra sau, nghĩa là thành công.\nCredentials saved to Keychain. Bây giờ thông tin xác thực đã được lưu trong Keychain, bạn không cần mật khẩu dành riêng cho app gốc nữa. (Tuy nhiên, nếu bạn có kế hoạch build trên các máy tính khác, tiện hơn nếu giữ nó trong trình quản lý mật khẩu.)\n3-3. Xác Nhận Kiểm tra xem hồ sơ đã lưu có thực sự hoạt động không.\nxcrun notarytool history --keychain-profile \u0026#34;focustimer-notarize\u0026#34; Nếu thông báo Successfully received submission history. xuất hiện, mọi thứ đều ổn. Lịch sử nộp bên dưới có thể trống (điều này bình thường vì bạn chưa công chứng gì cả) hoặc có thể chứa các bản ghi trước đó.\nBẫy Quan Trọng — Tài Khoản Apple ID và GitHub Khác Nhau Đây là điểm mà người thiết lập phân phối trực tiếp lần đầu thường vướng mắc nhất.\nTài khoản Apple Developer Program — dùng để cấp chứng chỉ và công chứng (ví dụ: developer@example.com) Tài khoản GitHub — dùng ở Phần 3 để lưu trữ file cập nhật (ví dụ: myname@gmail.com) Trong nhiều trường hợp, hai tài khoản này có email khác nhau. Đặc biệt, --apple-id của notarytool store-credentials phải là email tài khoản Apple Developer. Nếu bạn nhập email tài khoản GitHub, xác thực sẽ thất bại — và thông báo lỗi không thân thiện nên rất khó tìm ra nguyên nhân.\nNếu email của hai tài khoản dễ nhầm lẫn, chúng tôi khuyên bạn nên ghi chú xem tài khoản nào là dành cho Apple và tài khoản nào là dành cho GitHub. Sự phân biệt này xuất hiện lặp đi lặp lại trong suốt series.\nTổng Kết Phần 1 Nếu bạn đã làm theo đến đây, bây giờ bạn có:\n✅ Công cụ dòng lệnh để tự động hóa phát hành (gh, create-dmg) ✅ Tư cách thành viên Apple Developer Program đang hoạt động ✅ Chứng chỉ Developer ID Application được lưu trong Keychain ✅ Hồ sơ thông tin xác thực công chứng được lưu trong Keychain (focustimer-notarize) Với điều này, bạn đã sẵn sàng để ký và công chứng ứng dụng. Nhưng hầu như không có ứng dụng nào chỉ giao cho người dùng một lần rồi xong. Bạn phải tiếp tục phát hành các phiên bản mới sửa lỗi và thêm tính năng. Trên App Store, cập nhật tự động được xử lý cho bạn, nhưng với phân phối trực tiếp, đây cũng là thứ bạn phải tự thiết lập.\nỞ phần tiếp theo, chúng ta sẽ tạo khóa ký EdDSA cho Sparkle, framework cập nhật tự động de facto tiêu chuẩn cho các ứng dụng macOS. Đây là cơ chế xác minh dành riêng cho file cập nhật — một lớp riêng biệt, tách biệt với chứng chỉ.\n","permalink":"https://hobbyworker.me/vi/dev/2026-05-14-distribute-macos-app-1-developer-id-certificate/","summary":"\u003ch1 id=\"phân-phối-trực-tiếp-bằng-developer-id-nghĩa-là-gì\"\u003ePhân Phối Trực Tiếp Bằng Developer ID Nghĩa Là Gì\u003c/h1\u003e\n\u003cp\u003eCó hai cách chính để đưa ứng dụng macOS đến tay người dùng. Một là thông qua \u003cstrong\u003eMac App Store (MAS)\u003c/strong\u003e, hai là \u003cstrong\u003ephân phối trực tiếp (direct distribution)\u003c/strong\u003e — cho người dùng tải về file \u003ccode\u003e.dmg\u003c/code\u003e (hoặc \u003ccode\u003e.app\u003c/code\u003e) mà bạn tự tạo từ website, GitHub hay nơi tương tự.\u003c/p\u003e\n\u003cp\u003ePhân phối trực tiếp có những lợi thế rõ ràng. Bạn không cần chờ App Store phê duyệt, không mất phí hoa hồng, và có thể phát hành bản cập nhật bất cứ lúc nào theo cách mình muốn. Đổi lại, những việc mà App Store từng làm thay — \u003cstrong\u003eký code\u003c/strong\u003e, \u003cstrong\u003ecông chứng (notarization)\u003c/strong\u003e, và \u003cstrong\u003ecập nhật tự động\u003c/strong\u003e — giờ bạn phải tự lo liệu.\u003c/p\u003e","title":"Tự Phân Phối Ứng Dụng macOS (1): Chứng Chỉ Developer ID và Chuẩn Bị Công Chứng"},{"content":"Tổng quan Quy trình được mô tả trong bài viết này sẽ hướng dẫn bạn tạo một Quick Action bằng Automator trên macOS. Quick Action này có thể được sử dụng để chuyển đổi văn bản sang định dạng phù hợp hơn với tên file. Quá trình chuyển đổi sẽ loại bỏ các ký tự đặc biệt, chuyển văn bản thành chữ thường và thay thế khoảng trắng bằng dấu gạch ngang.\nQuy Trình Mở Automator Nhấn Cmd + Space để mở Spotlight, nhập \u0026ldquo;Automator\u0026rdquo; và nhấn Enter. Tạo Service mới Trong ứng dụng Automator, chọn \u0026ldquo;Quick Action\u0026rdquo; (trước đây gọi là \u0026ldquo;Service\u0026rdquo;) và nhấp \u0026ldquo;Choose\u0026rdquo;. Cấu hình Quick Action Ở đầu cửa sổ mới, thay đổi menu thả xuống \u0026ldquo;Workflow receives current\u0026rdquo; thành \u0026ldquo;text\u0026rdquo;. Đảm bảo menu thả xuống \u0026ldquo;in\u0026rdquo; được đặt thành \u0026ldquo;any application\u0026rdquo;. Thêm action \u0026ldquo;Run Shell Script\u0026rdquo; Trong thanh tìm kiếm bên trái, nhập \u0026ldquo;Run Shell Script\u0026rdquo; và kéo action vào bảng bên phải. Cấu hình action \u0026ldquo;Run Shell Script\u0026rdquo; Thay đổi \u0026ldquo;Pass input\u0026rdquo; thành \u0026ldquo;as arguments\u0026rdquo;. Dán script sau vào hộp văn bản: for text_input in \u0026#34;$@\u0026#34; do echo \u0026#34;$text_input\u0026#34; | sed \u0026#39;s/[^a-zA-Z0-9 ]//g\u0026#39; | tr \u0026#39;[:upper:]\u0026#39; \u0026#39;[:lower:]\u0026#39; | sed \u0026#39;s/ /-/g\u0026#39; done Thêm action \u0026ldquo;Copy to Clipboard\u0026rdquo; Trong thanh tìm kiếm bên trái, nhập \u0026ldquo;Copy to Clipboard\u0026rdquo; và kéo action vào bảng bên phải, bên dưới action \u0026ldquo;Run Shell Script\u0026rdquo;. Lưu Quick Action Nhấn Cmd + S và đặt tên cho Quick Action của bạn, ví dụ: \u0026ldquo;Convert Text to filename\u0026rdquo; Bây giờ script đã sẵn sàng để sử dụng từ menu ngữ cảnh nhấp chuột phải trong bất kỳ trình soạn thảo văn bản nào hỗ trợ macOS Services.\nCách sử dụng script:\nChọn một đoạn văn bản trong trình soạn thảo văn bản của bạn. Nhấp chuột phải vào văn bản đã chọn. Chuyển đến \u0026ldquo;Services\u0026rdquo; hoặc \u0026ldquo;Quick Actions\u0026rdquo; (tùy thuộc vào phiên bản macOS của bạn). Chọn action \u0026ldquo;Convert Text to filename\u0026rdquo;. Văn bản đã xử lý sẽ được sao chép vào clipboard, và bạn có thể dán vào bất kỳ đâu bạn cần.\nKết Luận Tạo một Quick Action trên macOS bằng Automator là cách tiện lợi để đơn giản hóa quy trình làm việc bằng cách tự động hóa các tác vụ lặp đi lặp lại, chẳng hạn như chuyển đổi văn bản sang định dạng phù hợp với tên file. Bài viết này cung cấp hướng dẫn chi tiết để tạo một Quick Action có thể được sử dụng trên nhiều ứng dụng macOS, trở thành công cụ thiết yếu cho bất kỳ ai muốn nâng cao năng suất và đơn giản hóa quy trình chỉnh sửa văn bản của mình.\n","permalink":"https://hobbyworker.me/vi/dev/2023-04-06-convert-text-to-filename-using-automator-on-macos/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eQuy trình được mô tả trong bài viết này sẽ hướng dẫn bạn tạo một Quick Action bằng Automator trên macOS. Quick Action này có thể được sử dụng để chuyển đổi văn bản sang định dạng phù hợp hơn với tên file. Quá trình chuyển đổi sẽ loại bỏ các ký tự đặc biệt, chuyển văn bản thành chữ thường và thay thế khoảng trắng bằng dấu gạch ngang.\u003c/p\u003e","title":"Chuyển Đổi Văn Bản Thành Tên File bằng Automator trên macOS"},{"content":"Tổng quan Hàm realtime_trending_searches() trong thư viện pytrends cho phép bạn lấy các tìm kiếm thịnh hành thời gian thực trên Google. Bằng cách phân tích dữ liệu này, bạn có thể nắm bắt được những xu hướng và chủ đề mới nhất đang thu hút sự chú ý của đối tượng khán giả, giúp tạo ra nội dung kịp thời, phù hợp và hấp dẫn.\nTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\nImport các thư viện cần thiết Thiết lập yêu cầu pytrends Lấy dữ liệu tìm kiếm thịnh hành thời gian thực Phân tích kết quả Lấy Dữ Liệu Tìm Kiếm Thịnh Hành Thời Gian Thực Đầu tiên, chúng ta cần import các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta lấy các tìm kiếm thịnh hành thời gian thực hiện tại trên Google bằng hàm realtime_trending_searches().\n# Retrieve real-time trending searches data realtime_trending_searches = pytrends.realtime_trending_searches(pn=\u0026#39;US\u0026#39;) Đoạn code này sẽ trả về một DataFrame chứa các tìm kiếm thịnh hành thời gian thực hiện tại tại Hoa Kỳ.\nPhân Tích Kết Quả Bây giờ chúng ta có thể phân tích dữ liệu tìm kiếm thịnh hành thời gian thực để xác định các xu hướng và chủ đề mới nhất đang thu hút sự chú ý của đối tượng khán giả.\n# Display the top 10 real-time trending searches print(realtime_trending_searches.head(10)) Đoạn code này sẽ hiển thị 10 tìm kiếm thịnh hành thời gian thực hàng đầu, cung cấp những thông tin quý giá về các xu hướng và chủ đề mới nhất đang thu hút sự chú ý của đối tượng khán giả.\nKết Luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm realtime_trending_searches() trong thư viện pytrends để khám phá các tìm kiếm thịnh hành thời gian thực trên Google. Bằng cách phân tích dữ liệu này, bạn có thể luôn nắm bắt được các xu hướng và chủ đề mới nhất, giúp tạo ra nội dung kịp thời, phù hợp và hấp dẫn. Hướng dẫn này đã đề cập đến toàn bộ quy trình thu thập và phân tích dữ liệu tìm kiếm thịnh hành thời gian thực, từ việc thiết lập yêu cầu pytrends đến phân tích kết quả. Tận dụng những thông tin này, bạn có thể tạo ra nội dung gây tiếng vang với đối tượng khán giả và khai thác các xu hướng hiện tại.\nNOTE : pytrends sử dụng API không chính thức. Nếu có vấn đề, vui lòng sử dụng đây.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-04-05-pytrends-11-discovering-realtime-trending-searches-for-uptotheminute-insights/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003erealtime_trending_searches()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn lấy các tìm kiếm thịnh hành thời gian thực trên Google. Bằng cách phân tích dữ liệu này, bạn có thể nắm bắt được những xu hướng và chủ đề mới nhất đang thu hút sự chú ý của đối tượng khán giả, giúp tạo ra nội dung kịp thời, phù hợp và hấp dẫn.\u003c/p\u003e\n\u003cp\u003eTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\u003c/p\u003e","title":"Pytrends 11: Khám Phá Tìm Kiếm Thịnh Hành Thời Gian Thực để Cập Nhật Thông Tin Tức Thì"},{"content":"Tổng quan Hàm suggestions() trong thư viện pytrends cho phép bạn lấy các gợi ý tìm kiếm cho một truy vấn cụ thể. Bằng cách phân tích các gợi ý này, bạn có thể khám phá các từ khóa và xu hướng mới liên quan đến truy vấn tìm kiếm, giúp tạo ra nội dung hấp dẫn và có mục tiêu hơn.\nTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\nImport các thư viện cần thiết Thiết lập yêu cầu pytrends Lấy dữ liệu gợi ý tìm kiếm Phân tích kết quả Lấy Dữ Liệu Gợi Ý Tìm Kiếm Đầu tiên, chúng ta cần import các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta lấy các gợi ý tìm kiếm cho một truy vấn cụ thể bằng hàm suggestions().\n# Retrieve search suggestions for the query \u0026#39;Python\u0026#39; suggestions = pytrends.suggestions(keyword=\u0026#39;Python\u0026#39;) Đoạn code này sẽ trả về một danh sách các dictionary chứa các gợi ý tìm kiếm liên quan đến truy vấn \u0026lsquo;Python\u0026rsquo;.\nPhân Tích Kết Quả Bây giờ chúng ta có thể phân tích dữ liệu gợi ý tìm kiếm để khám phá các từ khóa và xu hướng mới liên quan đến truy vấn tìm kiếm của mình.\n# Display the search suggestions for suggestion in suggestions: print(suggestion[\u0026#39;title\u0026#39;]) Đoạn code này sẽ hiển thị các gợi ý tìm kiếm liên quan đến truy vấn \u0026lsquo;Python\u0026rsquo;, cung cấp những thông tin quý giá về các từ khóa và xu hướng mới liên quan đến truy vấn tìm kiếm của bạn.\nKết Luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm suggestions() trong thư viện pytrends để tinh chỉnh các tìm kiếm xu hướng bằng cách lấy gợi ý tìm kiếm dựa trên một truy vấn nhất định. Bằng cách khám phá các gợi ý này, bạn có thể tìm ra các từ khóa và xu hướng mới liên quan đến truy vấn tìm kiếm, giúp tạo ra nội dung hấp dẫn và có mục tiêu hơn. Hướng dẫn này đã đề cập đến toàn bộ quy trình thu thập và phân tích dữ liệu gợi ý tìm kiếm, từ việc thiết lập yêu cầu pytrends đến phân tích kết quả. Tận dụng những thông tin này, bạn có thể nâng cao nghiên cứu từ khóa và chiến lược nội dung của mình.\nNOTE : pytrends sử dụng API không chính thức. Nếu có vấn đề, vui lòng sử dụng đây.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-04-04-pytrends-10-refining-trend-searches-with-suggestions/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003esuggestions()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn lấy các gợi ý tìm kiếm cho một truy vấn cụ thể. Bằng cách phân tích các gợi ý này, bạn có thể khám phá các từ khóa và xu hướng mới liên quan đến truy vấn tìm kiếm, giúp tạo ra nội dung hấp dẫn và có mục tiêu hơn.\u003c/p\u003e\n\u003cp\u003eTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\u003c/p\u003e","title":"Pytrends 10: Tinh Chỉnh Tìm Kiếm Xu Hướng bằng Gợi Ý"},{"content":"Tổng quan Hàm top_charts() trong thư viện pytrends cho phép bạn lấy bảng xếp hạng của Google cho một năm và danh mục cụ thể. Bằng cách phân tích dữ liệu này, bạn có thể khám phá các truy vấn tìm kiếm phổ biến nhất trong nhiều danh mục, giúp tạo ra nội dung hấp dẫn và phù hợp cho đối tượng khán giả của mình.\nTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\nImport các thư viện cần thiết Thiết lập yêu cầu pytrends Lấy dữ liệu bảng xếp hạng Phân tích kết quả Lấy Dữ Liệu Bảng Xếp Hạng Đầu tiên, chúng ta cần import các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta lấy dữ liệu bảng xếp hạng cho một năm và danh mục cụ thể bằng hàm top_charts().\n# Retrieve top charts data for 2022 top_charts = pytrends.top_charts(date=2022, hl=\u0026#39;en-US\u0026#39;, tz=360) Đoạn code này sẽ trả về một DataFrame chứa dữ liệu bảng xếp hạng năm 2022.\nPhân Tích Kết Quả Bây giờ chúng ta có thể phân tích dữ liệu bảng xếp hạng để xác định các truy vấn tìm kiếm phổ biến nhất trong danh mục đã chọn.\n# Display the top 10 in 2022 print(top_charts.head(10)) Đoạn code này sẽ hiển thị 10 mục hàng đầu năm 2022, cung cấp những thông tin quý giá về các truy vấn tìm kiếm phổ biến nhất.\nKết Luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm top_charts() trong thư viện pytrends để phân tích bảng xếp hạng của Google nhằm có được thông tin dựa trên dữ liệu. Bằng cách khám phá dữ liệu này, bạn có thể tìm ra các truy vấn tìm kiếm phổ biến nhất trong nhiều danh mục khác nhau, giúp tạo ra nội dung hấp dẫn và phù hợp cho đối tượng khán giả. Hướng dẫn này đã đề cập đến toàn bộ quy trình thu thập và phân tích dữ liệu bảng xếp hạng, từ việc thiết lập yêu cầu pytrends đến phân tích kết quả. Tận dụng những thông tin này, bạn có thể định hướng chiến lược nội dung và tối ưu hóa sự hiện diện trực tuyến của mình.\nNOTE : pytrends sử dụng API không chính thức. Nếu có vấn đề, vui lòng sử dụng đây.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-04-03-pytrends-9-mastering-top-charts-analysis-for-datadriven-insights/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003etop_charts()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn lấy bảng xếp hạng của Google cho một năm và danh mục cụ thể. Bằng cách phân tích dữ liệu này, bạn có thể khám phá các truy vấn tìm kiếm phổ biến nhất trong nhiều danh mục, giúp tạo ra nội dung hấp dẫn và phù hợp cho đối tượng khán giả của mình.\u003c/p\u003e\n\u003cp\u003eTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\u003c/p\u003e","title":"Pytrends 9: Thành Thạo Phân Tích Bảng Xếp Hạng để Có Thông Tin Dựa Trên Dữ Liệu"},{"content":"Tổng quan Hàm trending_searches() trong thư viện pytrends cho phép bạn lấy các tìm kiếm thịnh hành hiện tại trên Google. Bằng cách phân tích dữ liệu này, bạn có thể nắm bắt được những xu hướng và chủ đề mới nhất đang thu hút sự chú ý của đối tượng khán giả, giúp tạo ra nội dung kịp thời và phù hợp.\nTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\nImport các thư viện cần thiết Thiết lập yêu cầu pytrends Lấy dữ liệu tìm kiếm thịnh hành Phân tích kết quả Lấy Dữ Liệu Tìm Kiếm Thịnh Hành Đầu tiên, chúng ta cần import các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta lấy các tìm kiếm thịnh hành hiện tại trên Google bằng hàm trending_searches().\n# Retrieve trending searches data trending_searches = pytrends.trending_searches(pn=\u0026#39;united_states\u0026#39;) Đoạn code này sẽ trả về một DataFrame chứa các tìm kiếm thịnh hành hiện tại tại Hoa Kỳ.\nPhân Tích Kết Quả Bây giờ chúng ta có thể phân tích dữ liệu tìm kiếm thịnh hành để tìm ra những cơ hội mới cho việc tạo và tối ưu hóa nội dung.\n# Display the top 10 trending searches print(trending_searches.head(10)) Đoạn code này sẽ hiển thị 10 tìm kiếm thịnh hành hàng đầu, cung cấp những thông tin quý giá về các xu hướng và chủ đề mới nhất đang thu hút sự chú ý của đối tượng khán giả.\nKết Luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm trending_searches() trong thư viện pytrends để theo dõi các tìm kiếm thịnh hành trên Google. Bằng cách phân tích dữ liệu này, bạn có thể đón đầu xu hướng và khám phá những cơ hội mới cho việc tạo và tối ưu hóa nội dung. Hướng dẫn này đã đề cập đến toàn bộ quy trình thu thập và phân tích dữ liệu tìm kiếm thịnh hành, từ việc thiết lập yêu cầu pytrends đến phân tích kết quả. Tận dụng những thông tin này, bạn có thể tạo ra nội dung kịp thời và phù hợp cho đối tượng khán giả của mình.\nNOTE : pytrends sử dụng API không chính thức. Nếu có vấn đề, vui lòng sử dụng đây.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-04-02-pytrends-8-tracking-trending-searches-to-stay-ahead/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003etrending_searches()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn lấy các tìm kiếm thịnh hành hiện tại trên Google. Bằng cách phân tích dữ liệu này, bạn có thể nắm bắt được những xu hướng và chủ đề mới nhất đang thu hút sự chú ý của đối tượng khán giả, giúp tạo ra nội dung kịp thời và phù hợp.\u003c/p\u003e\n\u003cp\u003eTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\u003c/p\u003e","title":"Pytrends 8: Theo Dõi Các Tìm Kiếm Thịnh Hành để Đón Đầu Xu Hướng"},{"content":"Tổng quan Hàm related_queries() trong thư viện pytrends cho phép bạn lấy các truy vấn liên quan đến một từ khóa tìm kiếm cụ thể. Bằng cách phân tích dữ liệu này, bạn có thể hiểu rõ hơn về những câu hỏi và chủ đề quan trọng với đối tượng khán giả của mình, từ đó giúp tạo ra nội dung phù hợp và hấp dẫn hơn.\nTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\nImport các thư viện cần thiết Thiết lập yêu cầu pytrends Lấy dữ liệu truy vấn liên quan Phân tích kết quả Lấy Dữ Liệu Truy Vấn Liên Quan Đầu tiên, chúng ta cần import các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta chỉ định từ khóa tìm kiếm bằng hàm build_payload(), sau đó lấy dữ liệu truy vấn liên quan bằng hàm related_queries().\nkeywords = [\u0026#39;Python\u0026#39;] # Build payload pytrends.build_payload(keywords, timeframe=\u0026#39;now 7-d\u0026#39;, geo=\u0026#39;\u0026#39;) # Retrieve related queries data related_queries = pytrends.related_queries() Đoạn code này sẽ trả về một dictionary chứa dữ liệu truy vấn liên quan đến từ khóa \u0026lsquo;Python\u0026rsquo; trong 7 ngày qua.\nPhân Tích Kết Quả Bây giờ chúng ta có thể phân tích dữ liệu truy vấn liên quan để tìm ra những cơ hội mới cho việc tạo và tối ưu hóa nội dung.\n# Extract the related queries for the keyword \u0026#39;Python\u0026#39; python_related_queries = related_queries[keywords[0]][\u0026#39;rising\u0026#39;] # Display the top 10 rising related queries print(python_related_queries.head(10)) Đoạn code này sẽ hiển thị 10 truy vấn liên quan đang tăng nhanh nhất cho từ khóa \u0026lsquo;Python\u0026rsquo;, cung cấp những thông tin quý giá về các câu hỏi và chủ đề quan trọng với đối tượng khán giả của bạn.\nKết Luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm related_queries() trong thư viện pytrends để khám phá các truy vấn liên quan đến một từ khóa nhất định. Bằng cách phân tích dữ liệu này, bạn có thể thực hiện phân tích chuyên sâu các từ khóa mục tiêu và tìm ra những cơ hội mới cho việc tạo và tối ưu hóa nội dung. Hướng dẫn này đã đề cập đến toàn bộ quy trình thu thập và phân tích dữ liệu truy vấn liên quan, từ việc thiết lập yêu cầu pytrends đến phân tích kết quả. Tận dụng những thông tin này, bạn có thể tạo ra nội dung phù hợp và hấp dẫn hơn cho đối tượng khán giả của mình.\nNOTE : pytrends sử dụng API không chính thức. Nếu có vấn đề, vui lòng sử dụng đây.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-04-01-pytrends-7-uncovering-related-queries-for-indepth-analysis/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003erelated_queries()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn lấy các truy vấn liên quan đến một từ khóa tìm kiếm cụ thể. Bằng cách phân tích dữ liệu này, bạn có thể hiểu rõ hơn về những câu hỏi và chủ đề quan trọng với đối tượng khán giả của mình, từ đó giúp tạo ra nội dung phù hợp và hấp dẫn hơn.\u003c/p\u003e\n\u003cp\u003eTrong hướng dẫn này, chúng ta sẽ tìm hiểu:\u003c/p\u003e","title":"Pytrends 7: Khám Phá Các Truy Vấn Liên Quan để Phân Tích Chuyên Sâu"},{"content":"Tổng quan Hàm related_topics() trong thư viện pytrends cho phép bạn truy xuất các chủ đề liên quan đến một từ khóa tìm kiếm cụ thể. Bằng cách phân tích dữ liệu này, bạn có thể khám phá các từ khóa và ý tưởng mới phù hợp với đối tượng mục tiêu, giúp bạn tạo ra nội dung hấp dẫn và phù hợp.\nTrong hướng dẫn này, chúng ta sẽ đề cập đến:\nNhập các thư viện cần thiết Thiết lập yêu cầu pytrends Truy xuất dữ liệu chủ đề liên quan Phân tích kết quả Truy xuất dữ liệu chủ đề liên quan Trước tiên, chúng ta cần nhập các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta sẽ chỉ định từ khóa tìm kiếm cho yêu cầu bằng hàm build_payload(), sau đó truy xuất dữ liệu chủ đề liên quan bằng hàm related_topics().\nkeywords = [\u0026#39;Python\u0026#39;] # Build payload pytrends.build_payload(keywords, timeframe=\u0026#39;now 7-d\u0026#39;, geo=\u0026#39;\u0026#39;) # Retrieve related topics data related_topics = pytrends.related_topics() Hàm này sẽ trả về một dictionary chứa dữ liệu chủ đề liên quan cho từ khóa tìm kiếm \u0026lsquo;Python\u0026rsquo; trong 7 ngày qua.\nPhân tích kết quả Bây giờ, chúng ta có thể phân tích dữ liệu chủ đề liên quan để xác định các từ khóa và ý tưởng mới cho chiến lược nội dung.\n# Extract the related topics for the keyword \u0026#39;Python\u0026#39; python_related_topics = related_topics[keywords[0]][\u0026#39;top\u0026#39;] # Display the top 10 rising related topics print(python_related_topics.head(10)) Đoạn code này sẽ hiển thị top 10 chủ đề liên quan đang tăng trưởng mạnh cho từ khóa tìm kiếm \u0026lsquo;Python\u0026rsquo;, cung cấp những thông tin giá trị về các xu hướng mới nổi liên quan đến từ khóa của bạn.\nKết luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm related_topics() trong thư viện pytrends để điều tra các chủ đề liên quan đến một từ khóa tìm kiếm nhất định. Bằng cách khám phá dữ liệu này, bạn có thể mở rộng nghiên cứu từ khóa và khám phá các cơ hội mới để thu hút đối tượng mục tiêu. Hướng dẫn này đã đề cập đến quá trình thu thập và phân tích dữ liệu chủ đề liên quan, từ thiết lập yêu cầu pytrends đến phân tích kết quả. Sử dụng những thông tin này, bạn có thể thông báo cho chiến lược nội dung và tăng cường sự hiện diện trực tuyến của mình.\nLƯU Ý: pytrends sử dụng API không chính thức. Vui lòng dùng đây để báo cáo sự cố.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-31-pytrends-6-investigating-related-topics-to-expand-keyword-research/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003erelated_topics()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn truy xuất các chủ đề liên quan đến một từ khóa tìm kiếm cụ thể. Bằng cách phân tích dữ liệu này, bạn có thể khám phá các từ khóa và ý tưởng mới phù hợp với đối tượng mục tiêu, giúp bạn tạo ra nội dung hấp dẫn và phù hợp.\u003c/p\u003e\n\u003cp\u003eTrong hướng dẫn này, chúng ta sẽ đề cập đến:\u003c/p\u003e","title":"Pytrends 6: Nghiên cứu các chủ đề liên quan để mở rộng nghiên cứu từ khóa"},{"content":"Tổng quan Hàm interest_by_region() trong thư viện pytrends cho phép bạn truy xuất dữ liệu mức độ quan tâm cho các từ khóa tìm kiếm cụ thể trên các vị trí địa lý khác nhau. Bằng cách phân tích dữ liệu này, bạn có thể thu được những thông tin giá trị về mức độ phổ biến của từ khóa tìm kiếm ở các khu vực khác nhau, giúp thông báo cho chiến lược marketing và nội dung của bạn.\nTrong hướng dẫn này, chúng ta sẽ đề cập đến:\nNhập các thư viện cần thiết Thiết lập yêu cầu pytrends Truy xuất dữ liệu mức độ quan tâm theo khu vực Trực quan hóa kết quả Cài đặt Để cài đặt Pytrends, chỉ cần dùng pip:\npip install matplotlib Truy xuất dữ liệu mức độ quan tâm theo khu vực Trước tiên, chúng ta cần nhập các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq import pandas as pd # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta sẽ chỉ định từ khóa tìm kiếm và khoảng thời gian cho yêu cầu bằng hàm build_payload(), sau đó truy xuất dữ liệu mức độ quan tâm theo khu vực bằng hàm interest_by_region().\nkeywords = [\u0026#39;Python\u0026#39;] timeframe = \u0026#39;2023-01-01 2023-03-31\u0026#39; # Build payload pytrends.build_payload(keywords, timeframe=timeframe, geo=\u0026#39;\u0026#39;) # Retrieve interest by region data region_interest = pytrends.interest_by_region(resolution=\u0026#39;COUNTRY\u0026#39;, inc_low_vol=True, inc_geo_code=False) Hàm này sẽ trả về một DataFrame chứa dữ liệu mức độ quan tâm theo khu vực cho từ khóa tìm kiếm \u0026lsquo;Python\u0026rsquo; trong quý đầu năm 2023.\nTrực quan hóa kết quả Bây giờ, chúng ta có thể trực quan hóa dữ liệu mức độ quan tâm theo khu vực bằng biểu đồ cột.\nimport matplotlib.pyplot as plt # Sort the data by interest value region_interest = region_interest.sort_values(by=\u0026#39;Python\u0026#39;, ascending=False) # Plot the interest by region data plt.figure(figsize=(12, 6)) plt.bar(region_interest.index, region_interest[\u0026#39;Python\u0026#39;]) plt.xlabel(\u0026#39;Country\u0026#39;) plt.ylabel(\u0026#39;Interest\u0026#39;) plt.title(\u0026#39;Interest by Region for Python (Q1 2023)\u0026#39;) plt.xticks(rotation=90) plt.show() Biểu đồ này hiển thị mức độ quan tâm đến từ khóa tìm kiếm \u0026lsquo;Python\u0026rsquo; trên các quốc gia khác nhau, cho phép bạn xác định các khu vực mà từ khóa đó đặc biệt phổ biến.\nKết luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm interest_by_region() trong thư viện pytrends để phân tích mức độ quan tâm theo khu vực cho các từ khóa tìm kiếm cụ thể. Bằng cách khám phá dữ liệu này, bạn có thể thu thập những thông tin chuyên biệt về mức độ phổ biến của từ khóa tìm kiếm trên các vị trí địa lý khác nhau, giúp bạn hiểu rõ hơn về đối tượng của mình và tối ưu hóa chiến lược marketing. Hướng dẫn này đã đề cập đến quá trình thu thập và phân tích dữ liệu mức độ quan tâm theo khu vực, từ thiết lập yêu cầu pytrends đến trực quan hóa kết quả.\nLƯU Ý: pytrends sử dụng API không chính thức. Vui lòng dùng đây để báo cáo sự cố.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-30-pytrends-5-exploring-interest-by-region-for-targeted-insights/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003einterest_by_region()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn truy xuất dữ liệu mức độ quan tâm cho các từ khóa tìm kiếm cụ thể trên các vị trí địa lý khác nhau. Bằng cách phân tích dữ liệu này, bạn có thể thu được những thông tin giá trị về mức độ phổ biến của từ khóa tìm kiếm ở các khu vực khác nhau, giúp thông báo cho chiến lược marketing và nội dung của bạn.\u003c/p\u003e","title":"Pytrends 5: Khám phá mức độ quan tâm theo khu vực để có thông tin chuyên biệt"},{"content":"Tổng quan Hàm get_historical_interest() trong thư viện pytrends cho phép bạn truy xuất dữ liệu mức độ quan tâm theo giờ cho các từ khóa tìm kiếm cụ thể trong một khoảng thời gian nhất định. Điều này hữu ích để hiểu sâu hơn về mức độ phổ biến của từ khóa tìm kiếm và xác định các xu hướng có thể không hiển thị khi nhìn vào dữ liệu theo ngày hoặc theo tuần.\nTrong hướng dẫn này, chúng ta sẽ đề cập đến:\nNhập các thư viện cần thiết Thiết lập yêu cầu pytrends Truy xuất dữ liệu mức độ quan tâm theo giờ trong lịch sử Trực quan hóa kết quả Cài đặt Để cài đặt Pytrends, chỉ cần dùng pip:\npip install matplotlib Truy xuất dữ liệu mức độ quan tâm theo giờ trong lịch sử Để bắt đầu, chúng ta cần nhập các thư viện cần thiết và thiết lập yêu cầu pytrends.\nfrom pytrends.request import TrendReq import pandas as pd import matplotlib.pyplot as plt # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Tiếp theo, chúng ta sẽ chỉ định các từ khóa tìm kiếm, khoảng thời gian và các tham số khác cho yêu cầu bằng hàm get_historical_interest().\nkeywords = [\u0026#39;Python\u0026#39;, \u0026#39;JavaScript\u0026#39;] # Retrieve hourly interest data hourly_interest = pytrends.get_historical_interest(keywords, year_start=2023, month_start=3, day_start=1, hour_start=0, year_end=2023, month_end=3, day_end=2, hour_end=0, cat=0, geo=\u0026#39;\u0026#39;, gprop=\u0026#39;\u0026#39;, sleep=0) Hàm này sẽ trả về một DataFrame chứa dữ liệu mức độ quan tâm theo giờ cho các từ khóa tìm kiếm \u0026lsquo;Python\u0026rsquo; và \u0026lsquo;JavaScript\u0026rsquo; từ ngày 1 tháng 3 đến ngày 2 tháng 3 năm 2023.\nTrực quan hóa kết quả Bây giờ, chúng ta có thể trực quan hóa dữ liệu mức độ quan tâm theo giờ bằng một biểu đồ đường đơn giản.\n# Plot the hourly interest data plt.figure(figsize=(12, 6)) plt.plot(hourly_interest.index, hourly_interest[\u0026#39;Python\u0026#39;], label=\u0026#39;Python\u0026#39;) plt.plot(hourly_interest.index, hourly_interest[\u0026#39;JavaScript\u0026#39;], label=\u0026#39;JavaScript\u0026#39;) plt.xlabel(\u0026#39;Hour\u0026#39;) plt.ylabel(\u0026#39;Interest\u0026#39;) plt.title(\u0026#39;Hourly Interest for Python and JavaScript\u0026#39;) plt.legend() plt.show() Biểu đồ này hiển thị mức độ quan tâm theo giờ cho cả \u0026lsquo;Python\u0026rsquo; và \u0026lsquo;JavaScript\u0026rsquo; trong khoảng thời gian đã chỉ định, cho phép bạn so sánh mức độ phổ biến của chúng và xác định xu hướng.\nKết luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm get_historical_interest() trong thư viện pytrends để truy xuất dữ liệu mức độ quan tâm theo giờ trong lịch sử từ Google Trends. Bằng cách đi sâu vào dữ liệu này, bạn có thể thu được những thông tin giá trị về mức độ phổ biến của các từ khóa tìm kiếm và hiểu rõ hơn về hành vi của người tiêu dùng. Hướng dẫn này đã đề cập đến quá trình thu thập và phân tích dữ liệu mức độ quan tâm theo giờ, từ thiết lập yêu cầu pytrends đến trực quan hóa kết quả.\nLƯU Ý: pytrends sử dụng API không chính thức. Vui lòng dùng đây để báo cáo sự cố.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-29-pytrends-4-diving-into-historical-hourly-interest-data/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003eget_historical_interest()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn truy xuất dữ liệu mức độ quan tâm theo giờ cho các từ khóa tìm kiếm cụ thể trong một khoảng thời gian nhất định. Điều này hữu ích để hiểu sâu hơn về mức độ phổ biến của từ khóa tìm kiếm và xác định các xu hướng có thể không hiển thị khi nhìn vào dữ liệu theo ngày hoặc theo tuần.\u003c/p\u003e","title":"Pytrends 4: Đi sâu vào dữ liệu mức độ quan tâm theo giờ trong lịch sử"},{"content":"Tổng quan Hàm multirange_interest_over_time() trong thư viện pytrends cho phép bạn truy xuất mức độ quan tâm đến các từ khóa cụ thể trên nhiều khoảng thời gian. Bằng cách phân tích dữ liệu này, bạn có thể hiểu rõ hơn về sự thay đổi trong mức độ quan tâm đến một từ khóa qua các giai đoạn khác nhau, giúp bạn đưa ra quyết định sáng suốt về chiến lược nội dung và các hoạt động marketing.\nTrong hướng dẫn này, chúng ta sẽ đề cập đến:\nNhập các thư viện cần thiết Thiết lập yêu cầu pytrends Xây dựng danh sách khoảng thời gian Truy xuất dữ liệu mức độ quan tâm đa khoảng thời gian Phân tích kết quả Xây dựng danh sách khoảng thời gian Trước tiên, chúng ta cần tạo một danh sách các khoảng thời gian để phân tích mức độ quan tâm đến các từ khóa. Trong ví dụ này, chúng ta sẽ tạo một danh sách gồm hai khoảng thời gian.\ntime_ranges = [ \u0026#39;2022-09-04 2022-09-10\u0026#39;, \u0026#39;2022-09-18 2022-09-24\u0026#39;, ] Truy xuất dữ liệu mức độ quan tâm đa khoảng thời gian Tiếp theo, chúng ta cần nhập các thư viện cần thiết, thiết lập yêu cầu pytrends và truy xuất dữ liệu mức độ quan tâm đa khoảng thời gian cho các từ khóa và khoảng thời gian.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) # Define the list of keywords keywords = [\u0026#39;pizza\u0026#39;, \u0026#39;bagel\u0026#39;] # Build the payload pytrends.build_payload(keywords, timeframe=time_ranges) # Retrieve multi-range interest over time data interest_over_time_data = pytrends.multirange_interest_over_time() Hàm này sẽ trả về một dictionary chứa dữ liệu mức độ quan tâm theo thời gian cho các từ khóa trên các khoảng thời gian đã chỉ định.\nPhân tích kết quả Bây giờ, chúng ta có thể phân tích dữ liệu mức độ quan tâm đa khoảng thời gian để hiểu hiệu suất và mức độ phổ biến của các từ khóa qua các giai đoạn khác nhau.\n# Display the interest over time data print(interest_over_time_data) Đoạn code này sẽ hiển thị dữ liệu mức độ quan tâm theo thời gian cho mỗi khoảng thời gian, cung cấp những thông tin giá trị về hiệu suất và mức độ phổ biến của các từ khóa qua các giai đoạn khác nhau.\nKết luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm multirange_interest_over_time() trong thư viện pytrends để phân tích mức độ quan tâm đến các từ khóa cụ thể trên nhiều khoảng thời gian. Bằng cách phân tích dữ liệu này, bạn có thể có cái nhìn toàn diện hơn về xu hướng và hiểu rõ hơn về sự thay đổi trong mức độ quan tâm đến một từ khóa qua các giai đoạn khác nhau. Thông tin này có thể giúp bạn đưa ra quyết định sáng suốt về chiến lược nội dung, các hoạt động marketing và thậm chí phát triển sản phẩm. Bằng cách tận dụng phân tích mức độ quan tâm đa khoảng thời gian, bạn có thể đi trước đối thủ cạnh tranh và đảm bảo nội dung cũng như sản phẩm của bạn luôn phù hợp và hấp dẫn với đối tượng mục tiêu.\nLƯU Ý: pytrends sử dụng API không chính thức. Vui lòng dùng đây để báo cáo sự cố.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-28-pytrends-3-harnessing-multirange-interest-over-time-analysis/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003emultirange_interest_over_time()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn truy xuất mức độ quan tâm đến các từ khóa cụ thể trên nhiều khoảng thời gian. Bằng cách phân tích dữ liệu này, bạn có thể hiểu rõ hơn về sự thay đổi trong mức độ quan tâm đến một từ khóa qua các giai đoạn khác nhau, giúp bạn đưa ra quyết định sáng suốt về chiến lược nội dung và các hoạt động marketing.\u003c/p\u003e","title":"Pytrends 3: Khai thác phân tích mức độ quan tâm đa khoảng thời gian"},{"content":"Tổng quan Hàm interest_over_time() trong thư viện pytrends cho phép bạn truy xuất mức độ quan tâm đến các từ khóa cụ thể theo thời gian. Bằng cách phân tích dữ liệu này, bạn có thể hiểu rõ hơn về sự thay đổi trong mức độ quan tâm đến một từ khóa, giúp bạn đưa ra quyết định sáng suốt về chiến lược nội dung và các hoạt động marketing.\nTrong hướng dẫn này, chúng ta sẽ đề cập đến:\nXác định danh sách từ khóa Thiết lập khoảng thời gian Truy xuất dữ liệu mức độ quan tâm theo thời gian Phân tích kết quả Xác định danh sách từ khóa Trước tiên, chúng ta cần xác định danh sách các từ khóa mà chúng ta muốn phân tích mức độ quan tâm theo thời gian.\nkeywords = [\u0026#39;Python\u0026#39;, \u0026#39;JavaScript\u0026#39;] Thiết lập khoảng thời gian Tiếp theo, chúng ta cần thiết lập khoảng thời gian để phân tích mức độ quan tâm đến các từ khóa. Trong ví dụ này, chúng ta sẽ phân tích mức độ quan tâm trong một năm qua.\ntime_range = \u0026#39;2022-01-01 2023-01-31\u0026#39; Truy xuất dữ liệu mức độ quan tâm theo thời gian Bây giờ, chúng ta cần truy xuất dữ liệu mức độ quan tâm theo thời gian cho các từ khóa và khoảng thời gian bằng hàm interest_over_time().\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) # Build the payload pytrends.build_payload(keywords, cat=0, timeframe=time_range, geo=\u0026#39;\u0026#39;, gprop=\u0026#39;\u0026#39;) # Retrieve interest over time data interest_over_time_data = pytrends.interest_over_time() Hàm này sẽ trả về một DataFrame chứa dữ liệu mức độ quan tâm theo thời gian cho các từ khóa của chúng ta.\nPhân tích kết quả Bây giờ, chúng ta có thể phân tích dữ liệu mức độ quan tâm theo thời gian để hiểu hiệu suất và mức độ phổ biến của các từ khóa.\nprint(interest_over_time_data.head()) Đoạn code này sẽ hiển thị dữ liệu mức độ quan tâm theo thời gian, cung cấp những thông tin giá trị về hiệu suất và mức độ phổ biến của các từ khóa.\nKết luận Trong bài viết này, chúng ta đã minh họa cách sử dụng hàm interest_over_time() trong thư viện pytrends để phân tích mức độ quan tâm đến các từ khóa cụ thể theo thời gian. Bằng cách phân tích dữ liệu này, bạn có thể hiểu rõ hơn về sự thay đổi trong mức độ quan tâm đến một từ khóa, giúp bạn đưa ra quyết định sáng suốt về chiến lược nội dung, các hoạt động marketing và thậm chí phát triển sản phẩm. Bằng cách tận dụng phân tích mức độ quan tâm theo thời gian, bạn có thể đi trước đối thủ cạnh tranh và đảm bảo nội dung cũng như sản phẩm của bạn luôn phù hợp và hấp dẫn với đối tượng mục tiêu.\nLƯU Ý: pytrends sử dụng API không chính thức. Vui lòng dùng đây để báo cáo sự cố.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-27-pytrends-2-analyzing-interest-over-time/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHàm \u003ccode\u003einterest_over_time()\u003c/code\u003e trong thư viện \u003ccode\u003epytrends\u003c/code\u003e cho phép bạn truy xuất mức độ quan tâm đến các từ khóa cụ thể theo thời gian. Bằng cách phân tích dữ liệu này, bạn có thể hiểu rõ hơn về sự thay đổi trong mức độ quan tâm đến một từ khóa, giúp bạn đưa ra quyết định sáng suốt về chiến lược nội dung và các hoạt động marketing.\u003c/p\u003e","title":"Pytrends 2: Phân tích mức độ quan tâm theo thời gian"},{"content":"Tổng quan Google Trends cung cấp những thông tin chi tiết có giá trị về xu hướng tìm kiếm và mức độ phổ biến của từ khóa. Tuy nhiên, Google không cung cấp API chính thức để truy cập dữ liệu này. May mắn thay, thư viện Pytrends cho phép chúng ta truy cập dữ liệu Google Trends bằng Python.\nTrong hướng dẫn này, chúng ta sẽ hướng dẫn bạn qua quá trình cài đặt và thiết lập Pytrends, đồng thời trình bày cách thực hiện tìm kiếm đơn giản và diễn giải kết quả.\nYêu cầu trước Để theo dõi hướng dẫn này, bạn cần có:\nPython 3 đã được cài đặt Kiến thức cơ bản về lập trình Python Quen thuộc với việc sử dụng các gói Python Cài đặt Để cài đặt Pytrends, chỉ cần sử dụng pip:\npip install pytrends Thiết lập Pytrends Để bắt đầu sử dụng Pytrends, trước tiên hãy import các thư viện cần thiết và thiết lập kết nối với Google Trends:\nfrom pytrends.request import TrendReq pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Ở đây, chúng ta đặt ngôn ngữ là tiếng Anh (hl='en-US') và múi giờ là UTC+0 (tz=360).\nThực hiện tìm kiếm cơ bản Bây giờ, hãy thực hiện một tìm kiếm đơn giản để xem mức độ quan tâm theo thời gian đối với từ khóa \u0026ldquo;Python\u0026rdquo;:\nkeywords = [\u0026#39;Python\u0026#39;] pytrends.build_payload(keywords, timeframe=\u0026#39;today 5-y\u0026#39;, geo=\u0026#39;\u0026#39;, gprop=\u0026#39;\u0026#39;) interest_over_time_df = pytrends.interest_over_time() print(interest_over_time_df) Đoạn mã này định nghĩa một danh sách từ khóa, đặt khung thời gian là năm năm qua (timeframe='today 5-y') và để trống vị trí địa lý và thuộc tính Google. Phương thức interest_over_time() trả về một DataFrame chứa dữ liệu mức độ quan tâm.\nHiểu kết quả DataFrame kết quả chứa mức độ quan tâm tìm kiếm đối với từ khóa \u0026ldquo;Python\u0026rdquo; trong năm năm qua. Các giá trị đại diện cho mức độ quan tâm tìm kiếm tương đối so với điểm cao nhất trong khung thời gian được chỉ định, với 100 là đỉnh phổ biến.\nKết luận Trong bài đăng này, chúng ta đã giới thiệu Pytrends, một API Google Trends không chính thức dành cho Python, và trình bày cách cài đặt và thiết lập nó. Chúng ta đã thực hiện tìm kiếm cơ bản bằng thư viện và thảo luận về cách diễn giải kết quả.\nTrong các bài đăng sắp tới, chúng ta sẽ đi sâu hơn vào các chức năng nâng cao hơn của Pytrends, chẳng hạn như phân tích mức độ quan tâm theo thời gian, khám phá mức độ quan tâm theo khu vực và tìm kiếm các chủ đề và truy vấn liên quan. Hãy đón chờ!\nLƯU Ý : pytrends sử dụng API không chính thức. Nếu có vấn đề, vui lòng sử dụng tại đây.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-26-pytrends-1-how-to-use-google-trend-unofficially-with-python/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eGoogle Trends cung cấp những thông tin chi tiết có giá trị về xu hướng tìm kiếm và mức độ phổ biến của từ khóa. Tuy nhiên, Google không cung cấp API chính thức để truy cập dữ liệu này. May mắn thay, thư viện Pytrends cho phép chúng ta truy cập dữ liệu Google Trends bằng Python.\u003c/p\u003e\n\u003cp\u003eTrong hướng dẫn này, chúng ta sẽ hướng dẫn bạn qua quá trình cài đặt và thiết lập Pytrends, đồng thời trình bày cách thực hiện tìm kiếm đơn giản và diễn giải kết quả.\u003c/p\u003e","title":"Pytrends 1: Cách sử dụng Google Trends không chính thức với Python"},{"content":"Tổng quan rustup là trình cài đặt và quản lý toolchain chính thức cho ngôn ngữ lập trình Rust. Nó cung cấp một cách thuận tiện để cài đặt, cập nhật và quản lý nhiều toolchain Rust trên hệ thống của bạn. Báo cáo này sẽ đề cập đến quá trình cài đặt trên các nền tảng khác nhau, cách sử dụng cơ bản của rustup và cung cấp ví dụ về cách quản lý nhiều môi trường Rust.\nCài đặt macOS và Linux Để cài đặt rustup trên hệ thống macOS và Linux, hãy mở terminal và nhập lệnh sau:\ncurl --proto \u0026#39;=https\u0026#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh Script sẽ tải xuống và cài đặt các thành phần cần thiết. Sau khi hoàn tất, hãy khởi động lại terminal hoặc chạy lệnh sau để cập nhật biến môi trường của shell:\nsource $HOME/.cargo/env Windows Đối với người dùng Windows, hãy tải xuống và chạy file thực thi rustup-init.exe từ trang web chính thức của Rust. Làm theo hướng dẫn trên màn hình để hoàn tất cài đặt. Sau khi cài đặt hoàn tất, hãy khởi động lại command prompt hoặc terminal.\nCách sử dụng Cài đặt phiên bản Rust cụ thể Để cài đặt một phiên bản Rust cụ thể, hãy sử dụng lệnh sau:\nrustup install \u0026lt;version\u0026gt; Thay \u0026lt;version\u0026gt; bằng phiên bản Rust mong muốn, ví dụ 1.52.0.\nThiết lập phiên bản Rust mặc định Để thiết lập phiên bản Rust mặc định cho các dự án mới, hãy sử dụng lệnh sau:\nrustup default \u0026lt;version\u0026gt; Thay \u0026lt;version\u0026gt; bằng phiên bản Rust mong muốn, ví dụ 1.52.0.\nChuyển đổi giữa các phiên bản Rust Để chuyển đổi giữa các phiên bản Rust khác nhau cho một dự án cụ thể, hãy điều hướng đến thư mục dự án và sử dụng lệnh sau:\nrustup override set \u0026lt;version\u0026gt; Thay \u0026lt;version\u0026gt; bằng phiên bản Rust mong muốn, ví dụ 1.52.0.\nCập nhật Rust Để cập nhật tất cả các toolchain Rust đã cài đặt lên phiên bản mới nhất, hãy chạy lệnh sau:\nrustup update Gỡ cài đặt Rust Để gỡ cài đặt Rust và rustup khỏi hệ thống của bạn, hãy chạy lệnh sau:\nrustup self uninstall Ví dụ Giả sử bạn đang làm việc trên hai dự án Rust: project_old và project_new. project_old yêu cầu phiên bản Rust 1.52.0, trong khi project_new yêu cầu phiên bản stable mới nhất.\nĐầu tiên, hãy cài đặt các phiên bản Rust cần thiết:\nrustup install 1.52.0 rustup install stable Tiếp theo, điều hướng đến thư mục project_old và thiết lập phiên bản Rust cho dự án:\ncd project_old rustup override set 1.52.0 Bây giờ, điều hướng đến thư mục project_new và thiết lập phiên bản Rust cho dự án:\ncd project_new rustup override set stable Với các cấu hình này, mỗi dự án sẽ sử dụng phiên bản Rust phù hợp khi bạn build hoặc chạy chúng.\nVí dụ, khi bạn chạy cargo build hoặc cargo run trong thư mục project_old, Rust 1.52.0 sẽ được sử dụng:\ncd project_old cargo build Tương tự, khi bạn chạy cargo build hoặc cargo run trong thư mục project_new, phiên bản Rust stable mới nhất sẽ được sử dụng:\ncd project_new cargo build Với rustup, bạn có thể làm việc liền mạch trên nhiều dự án với các yêu cầu phiên bản Rust khác nhau mà không gặp xung đột hay cần can thiệp thủ công.\nKết luận rustup là công cụ thiết yếu cho các nhà phát triển Rust, vì nó đơn giản hóa quá trình quản lý nhiều môi trường Rust trên một hệ thống. Báo cáo này đã đề cập đến quá trình cài đặt trên các nền tảng khác nhau, cách sử dụng cơ bản và cung cấp ví dụ về cách quản lý các phiên bản Rust khác nhau cho nhiều dự án. Bằng cách sử dụng rustup, các nhà phát triển có thể đảm bảo rằng các dự án của họ luôn được build và chạy bằng phiên bản Rust đúng, cải thiện năng suất và giảm khả năng gặp các vấn đề liên quan đến phiên bản.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-26-managing-multiple-rust-environments-with-rustup/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003erustup\u003c/code\u003e là trình cài đặt và quản lý toolchain chính thức cho ngôn ngữ lập trình Rust. Nó cung cấp một cách thuận tiện để cài đặt, cập nhật và quản lý nhiều toolchain Rust trên hệ thống của bạn. Báo cáo này sẽ đề cập đến quá trình cài đặt trên các nền tảng khác nhau, cách sử dụng cơ bản của \u003ccode\u003erustup\u003c/code\u003e và cung cấp ví dụ về cách quản lý nhiều môi trường Rust.\u003c/p\u003e","title":"Quản lý nhiều môi trường Rust với rustup"},{"content":"Trong bài đăng blog này, chúng ta sẽ thảo luận về cách triển khai site tĩnh Hugo lên GitHub Pages bằng GitHub Actions. GitHub Actions là tính năng tự động hóa được cung cấp bởi GitHub, cho phép bạn tạo các workflow phát triển phần mềm tùy chỉnh trực tiếp trong repository GitHub của mình. Bằng cách sử dụng GitHub Actions, bạn có thể dễ dàng tự động hóa quá trình build và triển khai site tĩnh Hugo lên GitHub Pages.\nCấu hình cài đặt GitHub Pages Trước khi có thể triển khai thành công site tĩnh Hugo lên GitHub Pages bằng GitHub Actions, bạn cần cấu hình cài đặt GitHub Pages cho dự án của mình.\nVào trang dự án GitHub của bạn và nhấp vào tab \u0026ldquo;Settings\u0026rdquo; ở góc trên bên phải. Cuộn xuống phần \u0026ldquo;Pages\u0026rdquo;. Trong cài đặt \u0026ldquo;Build and deployment\u0026rdquo;, tìm menu thả xuống \u0026ldquo;Source\u0026rdquo;. Chọn \u0026ldquo;GitHub Actions\u0026rdquo; từ các tùy chọn có sẵn. Điều này hướng dẫn GitHub Pages sử dụng các artifact được tạo bởi GitHub Actions để triển khai. Bây giờ bạn đã cấu hình cài đặt GitHub Pages, site của bạn sẽ được triển khai bằng workflow được định nghĩa trong file hugo.yaml.\nThiết lập GitHub Pages Workflow Để thiết lập GitHub Pages workflow với Hugo và GitHub Actions, hãy làm theo các bước sau:\nTạo file mới có tên hugo.yaml trong thư mục .github/workflows/ của repository site Hugo của bạn. Sao chép cấu hình YAML sau vào file hugo.yaml: name: Deploy Hugo site to Pages on: # Runs on pushes targeting the default branch push: branches: - main # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: \u0026#34;pages\u0026#34; cancel-in-progress: false # Default to bash defaults: run: shell: bash jobs: # Build job build: runs-on: ubuntu-latest env: HUGO_VERSION: 0.111.3 steps: - name: Install Hugo CLI run: | wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \\ \u0026amp;\u0026amp; sudo dpkg -i ${{ runner.temp }}/hugo.deb - name: Install Dart Sass Embedded run: sudo snap install dart-sass-embedded - name: Checkout uses: actions/checkout@v3 with: submodules: recursive fetch-depth: 0 - name: Setup Pages id: pages uses: actions/configure-pages@v3 - name: Install Node.js dependencies run: \u0026#34;[[ -f package-lock.json || -f npm-shrinkwrap.json ]] \u0026amp;\u0026amp; npm ci || true\u0026#34; - name: Build with Hugo env: # For maximum backward compatibility with Hugo modules HUGO_ENVIRONMENT: production HUGO_ENV: production run: | hugo \\ --gc \\ --minify \\ --baseURL \u0026#34;${{ steps.pages.outputs.base_url }}/\u0026#34; - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: ./public # Deployment job deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 File cấu hình này thiết lập một workflow build site tĩnh Hugo và triển khai nó lên GitHub Pages khi bạn push lên nhánh main hoặc chạy workflow thủ công từ tab Actions.\nKết luận Với việc cấu hình cài đặt GitHub Pages trong dự án và sử dụng cấu hình workflow hugo.yaml được cung cấp, bạn có thể dễ dàng tự động hóa quá trình build và triển khai site tĩnh Hugo lên GitHub Pages. Bằng cách tận dụng GitHub Actions, bạn có thể tập trung vào việc tạo nội dung và cập nhật site mà không cần phải triển khai thủ công mỗi lần. Thiết lập này cũng cho phép bạn tận dụng các khả năng CI/CD tích hợp được cung cấp bởi GitHub Actions, giúp cải thiện hơn nữa quy trình phát triển của bạn.\nSource code : https://github.com/hobbyworker/hugo-demo\nDemo : https://hobbyworker.me/hugo-demo\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-25-deploying-a-hugo-static-site-to-github-pages-with-github-actions/","summary":"\u003cp\u003eTrong bài đăng blog này, chúng ta sẽ thảo luận về cách triển khai site tĩnh Hugo lên GitHub Pages bằng GitHub Actions. GitHub Actions là tính năng tự động hóa được cung cấp bởi GitHub, cho phép bạn tạo các workflow phát triển phần mềm tùy chỉnh trực tiếp trong repository GitHub của mình. Bằng cách sử dụng GitHub Actions, bạn có thể dễ dàng tự động hóa quá trình build và triển khai site tĩnh Hugo lên GitHub Pages.\u003c/p\u003e","title":"Triển khai site tĩnh Hugo lên GitHub Pages bằng GitHub Actions"},{"content":"Trong bài đăng blog này, chúng ta sẽ tìm hiểu cách thêm tính năng phát hiện AdBlocker vào blog Hugo của bạn sử dụng theme PaperMod. Chúng ta cũng sẽ bao gồm một thông báo cảnh báo đơn giản cho những người dùng đã bật trình chặn quảng cáo, khuyến khích họ tắt nó hoặc thêm trang web của bạn vào danh sách cho phép.\nTổng quan Dưới đây là tổng quan nhanh về các bước chúng ta sẽ thực hiện:\nTạo file CSS tùy chỉnh để tạo kiểu cho thông báo cảnh báo. Tạo file JavaScript để phát hiện trình chặn quảng cáo. Thêm file HTML partial cho thông báo cảnh báo. Mở rộng các partial head và footer để bao gồm các file mới của chúng ta. Hướng dẫn từng bước 1. Tạo file CSS tùy chỉnh Tạo file mới có tên custom_css.css trong thư mục assets/css/extended/ và dán đoạn mã CSS sau:\n#adblock-warning { background-color: #f2dede; color: #a94442; border-color: #ebccd1; padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; } 2. Tạo file JavaScript để phát hiện trình chặn quảng cáo Tạo file mới có tên adblock-detection.js trong thư mục static/js/ và dán đoạn mã JavaScript sau:\nfunction detectAdBlocker() { const adBlockTest = document.createElement(\u0026#39;div\u0026#39;); adBlockTest.innerHTML = \u0026#39;\u0026amp;nbsp;\u0026#39;; adBlockTest.className = \u0026#39;ad ads adbadge doubleclick BannerAd adsbox ad-placeholder ad-placement\u0026#39;; document.body.appendChild(adBlockTest); window.setTimeout(() =\u0026gt; { if (adBlockTest.offsetHeight === 0) { document.getElementById(\u0026#39;adblock-warning\u0026#39;).style.display = \u0026#39;block\u0026#39;; console.log(\u0026#39;Ad-blocker detected.\u0026#39;); } else { console.log(\u0026#39;No ad-blocker detected.\u0026#39;); } adBlockTest.remove(); }, 100); } window.addEventListener(\u0026#39;load\u0026#39;, detectAdBlocker); 3. Thêm file HTML partial cho thông báo cảnh báo Tạo file mới có tên adblock-warning.html trong thư mục layouts/partials/ và dán đoạn mã HTML sau:\n\u0026lt;div id=\u0026#34;adblock-warning\u0026#34; style=\u0026#34;display:none;\u0026#34;\u0026gt; \u0026lt;p\u0026gt;Please consider disabling your ad-blocker or whitelisting our website. Ads help support our content and keep it free for everyone. Thank you!\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; 4. Mở rộng các partial head và footer để bao gồm các file mới của chúng ta Chỉnh sửa file layouts/partials/extend_head.html và thêm dòng sau:\n\u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;{{ \u0026#34;css/extended/custom_css.css\u0026#34; | relURL }}\u0026#34;\u0026gt; Chỉnh sửa file layouts/partials/extend_footer.html và thêm dòng sau:\n\u0026lt;script src=\u0026#34;{{ \u0026#34;js/adblock-detection.js\u0026#34; | relURL }}\u0026#34; defer\u0026gt;\u0026lt;/script\u0026gt; Kết luận Sau khi hoàn thành các bước trên, bạn đã thêm thành công tính năng phát hiện AdBlocker vào blog Hugo của mình sử dụng theme PaperMod. Những khách truy cập đã bật trình chặn quảng cáo sẽ thấy thông báo cảnh báo lịch sự khuyến khích họ tắt nó hoặc thêm trang web của bạn vào danh sách cho phép, giúp hỗ trợ nội dung của bạn và giữ cho mọi người truy cập miễn phí.\nSource code : https://github.com/hobbyworker/hugo-demo\nDemo : https://hobbyworker.me/hugo-demo\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-24-adding-adblocker-detection-to-your-hugo-blog-with-papermod-theme/","summary":"\u003cp\u003eTrong bài đăng blog này, chúng ta sẽ tìm hiểu cách thêm tính năng phát hiện AdBlocker vào blog Hugo của bạn sử dụng theme PaperMod. Chúng ta cũng sẽ bao gồm một thông báo cảnh báo đơn giản cho những người dùng đã bật trình chặn quảng cáo, khuyến khích họ tắt nó hoặc thêm trang web của bạn vào danh sách cho phép.\u003c/p\u003e\n\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eDưới đây là tổng quan nhanh về các bước chúng ta sẽ thực hiện:\u003c/p\u003e","title":"Thêm tính năng phát hiện AdBlocker vào blog Hugo với theme PaperMod"},{"content":"Trong bài đăng blog này, chúng ta sẽ tìm hiểu cách sử dụng nvm (Node Version Manager) và autoenv cùng nhau để quản lý các phiên bản Node.js và biến môi trường trong quy trình phát triển. Hướng dẫn này giả sử bạn đã cài đặt cả nvm và autoenv trên hệ thống của mình.\nTại sao nên dùng NVM và Autoenv cùng nhau? nvm là một công cụ tuyệt vời để quản lý nhiều phiên bản Node.js trên hệ thống của bạn, cho phép bạn dễ dàng chuyển đổi giữa chúng. autoenv đơn giản hóa quá trình quản lý biến môi trường bằng cách tự động tải chúng từ file .env khi bạn vào một thư mục.\nBằng cách kết hợp hai công cụ này, bạn có thể thiết lập môi trường phát triển để tự động chuyển sang phiên bản Node.js phù hợp và tải các biến môi trường liên quan, giúp tối ưu hóa quy trình làm việc của bạn.\nTạo file .env Đầu tiên, hãy tạo file .env trong thư mục gốc của dự án. File này sẽ chứa các biến môi trường và phiên bản Node.js mà bạn muốn sử dụng cho dự án.\nDưới đây là ví dụ về nội dung file .env:\nexport NODE_ENV=development export API_KEY=your_api_key_here export PORT=3000 export NVM_DIR=\u0026#34;$HOME/.nvm\u0026#34; nvm use 14.17.0 Trong ví dụ này, chúng ta đang thiết lập các biến môi trường NODE_ENV, API_KEY và PORT. Chúng ta cũng chỉ định đường dẫn đến thư mục nvm và hướng dẫn sử dụng phiên bản Node.js 14.17.0 cho dự án.\nSử dụng NVM với Autoenv Bây giờ bạn đã thiết lập file .env, bạn cần cấu hình autoenv để hoạt động cùng nvm. Để làm điều này, hãy thêm dòng sau vào file .autoenv.zsh hoặc .autoenv.sh của bạn, tùy thuộc vào shell bạn đang dùng:\nsource \u0026#34;$NVM_DIR/nvm.sh\u0026#34; Dòng này đảm bảo rằng lệnh nvm khả dụng khi autoenv tải file .env.\nThiết lập dự án của bạn Sau khi hoàn tất cấu hình, hãy điều hướng đến thư mục gốc của dự án bằng terminal. Bạn sẽ thấy thông báo từ autoenv cho biết nó đã tải file .env:\n$ cd your_project_directory autoenv: autoenv: Loading .env autoenv: Switching to Node.js v14.17.0 Bây giờ, phiên bản Node.js đã chỉ định và các biến môi trường từ file .env sẽ được tự động thiết lập cho dự án của bạn.\nChuyển đổi giữa các dự án Khi bạn di chuyển giữa các dự án có file .env khác nhau, autoenv và nvm sẽ tự động điều chỉnh phiên bản Node.js và biến môi trường tương ứng:\n$ cd another_project_directory autoenv: autoenv: Loading .env autoenv: Switching to Node.js v12.22.1 Điều này giúp việc quản lý các phiên bản Node.js và môi trường khác nhau trở nên cực kỳ đơn giản!\nKết luận Bằng cách sử dụng nvm và autoenv kết hợp với nhau, bạn có thể đơn giản hóa đáng kể việc quản lý phiên bản Node.js và biến môi trường cho các dự án của mình. Điều này sẽ giúp quy trình phát triển của bạn hiệu quả hơn và đảm bảo rằng bạn luôn sử dụng đúng cấu hình cho từng dự án.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-23-using-nvm-and-autoenv-in-combination/","summary":"\u003cp\u003eTrong bài đăng blog này, chúng ta sẽ tìm hiểu cách sử dụng \u003ccode\u003envm\u003c/code\u003e (Node Version Manager) và \u003ccode\u003eautoenv\u003c/code\u003e cùng nhau để quản lý các phiên bản Node.js và biến môi trường trong quy trình phát triển. Hướng dẫn này giả sử bạn đã cài đặt cả \u003ccode\u003envm\u003c/code\u003e và \u003ccode\u003eautoenv\u003c/code\u003e trên hệ thống của mình.\u003c/p\u003e\n\u003ch1 id=\"tại-sao-nên-dùng-nvm-và-autoenv-cùng-nhau\"\u003eTại sao nên dùng NVM và Autoenv cùng nhau?\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003envm\u003c/code\u003e là một công cụ tuyệt vời để quản lý nhiều phiên bản Node.js trên hệ thống của bạn, cho phép bạn dễ dàng chuyển đổi giữa chúng. \u003ccode\u003eautoenv\u003c/code\u003e đơn giản hóa quá trình quản lý biến môi trường bằng cách tự động tải chúng từ file \u003ccode\u003e.env\u003c/code\u003e khi bạn vào một thư mục.\u003c/p\u003e","title":"Sử dụng NVM và Autoenv kết hợp với nhau"},{"content":"Trong bài viết này, chúng ta sẽ thảo luận về cách sử dụng jEnv và autoenv kết hợp để quản lý nhiều phiên bản Java và tự động thiết lập biến môi trường cho các dự án của bạn. Hướng dẫn này giả định rằng bạn đã cài đặt jEnv và autoenv trên hệ thống. Chúng ta sẽ đi qua một số ví dụ thực tế để minh họa cách sử dụng chúng.\nTổng quan về jEnv và autoenv jEnv jEnv là công cụ dòng lệnh giúp đơn giản hóa việc quản lý nhiều bản cài đặt Java trên hệ thống của bạn. Nó cho phép bạn chuyển đổi dễ dàng giữa các phiên bản Java khác nhau, đặt phiên bản toàn cục hoặc cục bộ, và cung cấp cách thuận tiện để cấu hình môi trường của bạn.\nautoenv autoenv là công cụ dòng lệnh tự động thiết lập biến môi trường khi bạn điều hướng đến thư mục dự án. Nó hoạt động bằng cách tìm kiếm file .env trong thư mục dự án và thực thi nội dung của nó.\nCấu hình jEnv Trước khi đi vào các ví dụ, hãy tìm hiểu sơ lược về cách cấu hình jEnv. Đầu tiên, thêm các phiên bản Java đã cài đặt vào jEnv bằng lệnh jenv add:\njenv add /path/to/java/version Để liệt kê tất cả các phiên bản Java đã thêm, sử dụng:\njenv versions Để đặt phiên bản Java toàn cục, sử dụng:\njenv global \u0026lt;version\u0026gt; Để đặt phiên bản Java cục bộ cho một dự án cụ thể, điều hướng đến thư mục dự án và sử dụng:\njenv local \u0026lt;version\u0026gt; Làm việc với jEnv và autoenv Bây giờ, hãy xem một số ví dụ thực tế về cách sử dụng jEnv và autoenv kết hợp.\nExample 1: Đặt phiên bản Java cho dự án Tạo file .env trong thư mục dự án của bạn với nội dung sau:\n# .env export JAVA_HOME=$(jenv prefix) Khi bạn điều hướng đến thư mục dự án, autoenv sẽ tự động đặt biến môi trường JAVA_HOME theo phiên bản Java hiện đang hoạt động được cấu hình bởi jEnv.\nExample 2: Đặt phiên bản Java và các biến môi trường khác Giả sử bạn có dự án yêu cầu một phiên bản Java cụ thể và một số biến môi trường bổ sung. Tạo file .env trong thư mục dự án với nội dung sau:\n# .env jenv local \u0026lt;version\u0026gt; export JAVA_HOME=$(jenv prefix) export APP_ENV=development export API_KEY=your_api_key Khi bạn điều hướng đến thư mục dự án, autoenv sẽ tự động đặt phiên bản Java cục bộ, JAVA_HOME và các biến môi trường cần thiết khác.\nExample 3: Hủy thiết lập biến môi trường khi rời khỏi thư mục dự án Để hủy thiết lập biến môi trường khi rời khỏi thư mục dự án, hãy tạo file .env.leave trong thư mục dự án với nội dung sau:\n# .env.leave unset JAVA_HOME unset APP_ENV unset API_KEY Bây giờ, khi bạn điều hướng ra khỏi thư mục dự án, autoenv sẽ tự động hủy thiết lập các biến môi trường.\nKết luận Bằng cách sử dụng jEnv và autoenv kết hợp, bạn có thể đơn giản hóa việc quản lý các phiên bản Java và biến môi trường cho các dự án của mình. Điều này giúp tạo ra môi trường phát triển nhất quán và dễ dự đoán hơn, giúp việc làm việc trên nhiều dự án với các phiên bản và cấu hình Java khác nhau trở nên dễ dàng hơn. Ngoài ra, các công cụ này tự động hóa quá trình chuyển đổi giữa các môi trường Java khác nhau, giảm thời gian cấu hình thủ công và giảm thiểu các lỗi tiềm ẩn.\nHơn nữa, bằng cách tận dụng sức mạnh của jEnv và autoenv, các nhà phát triển có thể cộng tác hiệu quả hơn với các thành viên trong nhóm, vì quy trình thiết lập trở nên đơn giản và có thể tái tạo. Điều này cho phép việc onboard thành viên mới diễn ra suôn sẻ hơn và thúc đẩy quy trình phát triển thống nhất.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-22-how-to-use-jenv-and-autoenv-in-combination/","summary":"\u003cp\u003eTrong bài viết này, chúng ta sẽ thảo luận về cách sử dụng \u003ccode\u003ejEnv\u003c/code\u003e và \u003ccode\u003eautoenv\u003c/code\u003e kết hợp để quản lý nhiều phiên bản Java và tự động thiết lập biến môi trường cho các dự án của bạn. Hướng dẫn này giả định rằng bạn đã cài đặt \u003ccode\u003ejEnv\u003c/code\u003e và \u003ccode\u003eautoenv\u003c/code\u003e trên hệ thống. Chúng ta sẽ đi qua một số ví dụ thực tế để minh họa cách sử dụng chúng.\u003c/p\u003e","title":"Cách sử dụng jEnv và autoenv kết hợp với nhau"},{"content":"Trong bài viết này, chúng ta sẽ thảo luận về cách kết hợp sức mạnh của rbenv và autoenv để quản lý các phiên bản Ruby và biến môi trường một cách liền mạch trong các dự án của bạn. Sau khi đọc xong bài này, bạn sẽ hiểu rõ cách sử dụng hai công cụ này cùng nhau để cải thiện hơn nữa trải nghiệm phát triển Ruby của mình.\nLưu ý: Hướng dẫn này giả định rằng bạn đã cài đặt rbenv và autoenv. Nếu chưa, hãy làm theo hướng dẫn cài đặt cho rbenv và autoenv.\nTại sao nên dùng rbenv và autoenv cùng nhau? rbenv là công cụ mạnh mẽ giúp bạn quản lý nhiều phiên bản Ruby một cách dễ dàng. Mặt khác, autoenv giúp bạn quản lý các biến môi trường riêng theo từng thư mục dự án. Bằng cách sử dụng cả hai công cụ cùng nhau, bạn có thể đảm bảo rằng mình luôn sử dụng phiên bản Ruby và biến môi trường đúng cho mỗi dự án mà không cần can thiệp thủ công.\nCấu hình rbenv và autoenv Trước khi đi vào các ví dụ, hãy cấu hình autoenv để hoạt động cùng với rbenv. Để làm điều này, hãy tạo file .env mới trong thư mục dự án và thêm các dòng sau:\nexport RBENV_VERSION=$(cat .ruby-version) export PATH=\u0026#34;$HOME/.rbenv/shims:$PATH\u0026#34; Dòng đầu tiên đặt biến môi trường RBENV_VERSION dựa trên nội dung của file .ruby-version. Dòng thứ hai đảm bảo thư mục shims của rbenv nằm trong PATH, để phiên bản Ruby đúng được sử dụng khi chạy các lệnh Ruby.\nExample 1: Chuyển đổi phiên bản Ruby Giả sử bạn có hai dự án: project_a và project_b. Bạn muốn sử dụng Ruby phiên bản 2.7.4 cho project_a và Ruby phiên bản 3.0.2 cho project_b. Đây là cách thực hiện với rbenv và autoenv:\nTạo file .ruby-version trong mỗi thư mục dự án: echo \u0026#34;2.7.4\u0026#34; \u0026gt; project_a/.ruby-version echo \u0026#34;3.0.2\u0026#34; \u0026gt; project_b/.ruby-version Tạo file .env trong mỗi thư mục dự án với nội dung đã đề cập ở trên: cp .env project_a/ cp .env project_b/ Bây giờ, khi bạn điều hướng đến project_a hoặc project_b, autoenv sẽ tự động đặt RBENV_VERSION và điều chỉnh PATH để sử dụng phiên bản Ruby đúng.\nExample 2: Quản lý biến môi trường riêng theo dự án Giả sử project_a yêu cầu các biến môi trường sau:\nAPI_KEY: Khóa API của bạn cho dịch vụ bên thứ ba SECRET_KEY: Khóa bí mật để mã hóa dữ liệu Bạn có thể thêm các biến này vào file .env trong project_a như sau:\nexport RBENV_VERSION=$(cat .ruby-version) export PATH=\u0026#34;$HOME/.rbenv/shims:$PATH\u0026#34; export API_KEY=\u0026#34;your_api_key_here\u0026#34; export SECRET_KEY=\u0026#34;your_secret_key_here\u0026#34; Bây giờ, khi bạn điều hướng đến project_a, autoenv sẽ tự động đặt RBENV_VERSION, điều chỉnh PATH và thiết lập các biến môi trường API_KEY và SECRET_KEY.\nKết luận Bằng cách sử dụng rbenv và autoenv cùng nhau, bạn có thể dễ dàng quản lý các phiên bản Ruby và biến môi trường riêng theo dự án mà không cần can thiệp thủ công. Sự kết hợp này làm cho trải nghiệm phát triển Ruby của bạn hiệu quả hơn và ít xảy ra lỗi hơn.\nĐừng quên thêm file .env vào .gitignore để tránh vô tình commit thông tin nhạy cảm vào hệ thống quản lý phiên bản của bạn.\nVới thiết lập này, bạn sẽ tận hưởng quy trình phát triển Ruby mượt mà được tùy chỉnh cho từng dự án. Chúc bạn lập trình vui vẻ!\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-21-how-to-use-rbenv-and-autoenv-in-combination/","summary":"\u003cp\u003eTrong bài viết này, chúng ta sẽ thảo luận về cách kết hợp sức mạnh của \u003ccode\u003erbenv\u003c/code\u003e và \u003ccode\u003eautoenv\u003c/code\u003e để quản lý các phiên bản Ruby và biến môi trường một cách liền mạch trong các dự án của bạn. Sau khi đọc xong bài này, bạn sẽ hiểu rõ cách sử dụng hai công cụ này cùng nhau để cải thiện hơn nữa trải nghiệm phát triển Ruby của mình.\u003c/p\u003e","title":"Cách sử dụng rbenv và autoenv kết hợp với nhau"},{"content":"I. Tổng quan Trong bài viết này, chúng ta sẽ khám phá cách sử dụng pyenv-virtualenv và autoenv cùng nhau để phát triển Python liền mạch. Các công cụ này giúp bạn quản lý nhiều môi trường Python và môi trường ảo một cách dễ dàng, cải thiện quy trình phát triển của bạn.\nII. Pyenv-virtualenv Pyenv-virtualenv là plugin cho pyenv cho phép bạn tạo và quản lý môi trường ảo cho các phiên bản Python khác nhau. Nó giúp giữ các phụ thuộc của các dự án khác nhau tách biệt, đảm bảo mỗi dự án có thể truy cập các gói cần thiết mà không bị xung đột.\nTạo môi trường ảo Để tạo môi trường ảo mới bằng pyenv-virtualenv, sử dụng lệnh sau:\npyenv virtualenv \u0026lt;python-version\u0026gt; \u0026lt;virtualenv-name\u0026gt; Ví dụ, nếu bạn muốn tạo môi trường ảo tên my_project sử dụng Python 3.8.0:\npyenv virtualenv 3.8.0 my_project Liệt kê các môi trường ảo Để liệt kê tất cả môi trường ảo bạn đã tạo, sử dụng lệnh sau:\npyenv virtualenvs Kích hoạt môi trường ảo Để kích hoạt một môi trường ảo, sử dụng lệnh sau:\npyenv activate \u0026lt;virtualenv-name\u0026gt; Ví dụ:\npyenv activate my_project Hủy kích hoạt môi trường ảo Để hủy kích hoạt môi trường ảo hiện tại, sử dụng lệnh sau:\npyenv deactivate III. Autoenv Autoenv là công cụ tự động kích hoạt môi trường ảo khi bạn vào thư mục chứa file .env. Điều này giúp bạn dễ dàng chuyển đổi giữa các dự án mà không cần nhớ phải kích hoạt và hủy kích hoạt môi trường ảo thủ công.\nCấu hình Autoenv Để sử dụng autoenv, bạn cần tạo file .env trong thư mục gốc của dự án. File này sẽ chứa các lệnh cần được thực thi khi bạn vào thư mục.\nVí dụ, giả sử bạn có dự án tại ~/projects/my_project và muốn sử dụng môi trường ảo my_project đã tạo trước đó. Hãy tạo file .env trong thư mục ~/projects/my_project với nội dung sau:\nsource $(pyenv root)/versions/my_project/bin/activate Sử dụng Autoenv Bây giờ, khi bạn điều hướng đến thư mục dự án, autoenv sẽ tự động kích hoạt môi trường ảo my_project cho bạn:\ncd ~/projects/my_project Bạn sẽ thấy thông báo cho biết môi trường ảo đã được kích hoạt:\nautoenv: Activating environment . . . (my_project) $ Khi bạn rời khỏi thư mục dự án, môi trường ảo sẽ tự động bị hủy kích hoạt:\ncd ~ Bạn sẽ thấy thông báo cho biết môi trường ảo đã bị hủy kích hoạt:\nautoenv: Deactivating environment . . . $ IV. Kết luận Bằng cách kết hợp pyenv-virtualenv và autoenv, bạn có thể tạo ra một quy trình phát triển liền mạch để quản lý nhiều dự án Python. Cách tiếp cận này đảm bảo bạn luôn sử dụng môi trường ảo đúng cho từng dự án, đồng thời giữ các phụ thuộc tách biệt và có tổ chức.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-20-using-pyenv-virtualenv-and-autoenv-in-combination-for-python-development/","summary":"\u003ch1 id=\"i-tổng-quan\"\u003eI. Tổng quan\u003c/h1\u003e\n\u003cp\u003eTrong bài viết này, chúng ta sẽ khám phá cách sử dụng \u003ccode\u003epyenv-virtualenv\u003c/code\u003e và \u003ccode\u003eautoenv\u003c/code\u003e cùng nhau để phát triển Python liền mạch. Các công cụ này giúp bạn quản lý nhiều môi trường Python và môi trường ảo một cách dễ dàng, cải thiện quy trình phát triển của bạn.\u003c/p\u003e\n\u003ch1 id=\"ii-pyenv-virtualenv\"\u003eII. Pyenv-virtualenv\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003ePyenv-virtualenv\u003c/code\u003e là plugin cho \u003ccode\u003epyenv\u003c/code\u003e cho phép bạn tạo và quản lý môi trường ảo cho các phiên bản Python khác nhau. Nó giúp giữ các phụ thuộc của các dự án khác nhau tách biệt, đảm bảo mỗi dự án có thể truy cập các gói cần thiết mà không bị xung đột.\u003c/p\u003e","title":"Kết hợp Pyenv-virtualenv và Autoenv cho phát triển Python"},{"content":"I. Tổng quan Autoenv là công cụ giúp bạn dễ dàng quản lý các biến môi trường. Với công cụ này, bạn có thể tự động thiết lập biến môi trường khi vào một thư mục và hủy thiết lập khi rời khỏi. Điều này biến nó trở thành một công cụ mạnh mẽ để quản lý các môi trường khác nhau cho từng dự án và tự động hóa các tác vụ lặp lại. Trong bài viết này, chúng ta sẽ tìm hiểu cách cài đặt Autoenv trên các nền tảng khác nhau và một số ví dụ sử dụng thực tế.\nII. Cài đặt Mac Autoenv có thể được cài đặt dễ dàng trên macOS bằng Homebrew. Trước tiên, hãy đảm bảo bạn đã có Homebrew. Sau đó, chạy lệnh sau:\nbrew install autoenv Linux Autoenv có thể được cài đặt trên hệ thống Linux bằng apt-get. Chạy lệnh sau:\nsudo apt-get install autoenv Windows Autoenv có thể được cài đặt trên Windows bằng Git Bash hoặc WSL. Bạn có thể tải Git Bash từ trang web Git hoặc bật WSL trên Windows 10. Sau khi cài đặt xong, bạn có thể làm theo hướng dẫn cài đặt trên Linux.\nIII. Ví dụ sử dụng Sau khi Autoenv được cài đặt, bạn có thể sử dụng nó để quản lý biến môi trường cho các dự án khác nhau. Hãy cùng xem một số ví dụ.\nExample 1: Thiết lập biến môi trường Giả sử bạn đang làm việc trên một dự án Python yêu cầu các biến môi trường cụ thể được thiết lập. Bạn có thể tạo file .env trong thư mục dự án với nội dung sau:\nexport API_KEY=my_api_key export DATABASE_URL=postgres://user:password@localhost/mydatabase Bây giờ, khi bạn vào thư mục dự án, Autoenv sẽ tự động thiết lập các biến môi trường này. Bạn có thể truy cập chúng từ code Python bằng os.environ.\nExample 2: Chạy lệnh khi vào thư mục Bạn cũng có thể cấu hình Autoenv để chạy lệnh khi vào một thư mục. Ví dụ, giả sử bạn luôn muốn kích hoạt môi trường ảo khi vào thư mục dự án. Bạn có thể thêm dòng sau vào file .env:\nsource venv/bin/activate Bây giờ, khi bạn vào thư mục dự án, Autoenv sẽ tự động kích hoạt môi trường ảo.\nExample 3: Hủy thiết lập biến môi trường Khi rời khỏi một thư mục, Autoenv cũng có thể hủy thiết lập các biến môi trường bạn đã đặt. Điều này hữu ích để dọn dẹp sau khi làm việc và đảm bảo các biến môi trường không bị rò rỉ sang các dự án khác. Để làm điều này, thêm dòng sau vào file .env:\nunset API_KEY unset DATABASE_URL Bây giờ, khi bạn rời khỏi thư mục dự án, Autoenv sẽ tự động hủy thiết lập các biến môi trường.\nIV. Kết luận Autoenv là công cụ mạnh mẽ để quản lý biến môi trường và tự động hóa các tác vụ lặp lại. Với công cụ này, bạn có thể dễ dàng thiết lập và hủy thiết lập biến môi trường, chạy lệnh khi vào thư mục và nhiều hơn nữa. Bằng cách cài đặt Autoenv trên hệ thống và sử dụng nó trong các dự án, bạn có thể tiết kiệm thời gian và tối ưu hóa quy trình làm việc của mình.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-19-using-autoenv-the-ultimate-shortcut-to-environment-management/","summary":"\u003ch1 id=\"i-tổng-quan\"\u003eI. Tổng quan\u003c/h1\u003e\n\u003cp\u003eAutoenv là công cụ giúp bạn dễ dàng quản lý các biến môi trường. Với công cụ này, bạn có thể tự động thiết lập biến môi trường khi vào một thư mục và hủy thiết lập khi rời khỏi. Điều này biến nó trở thành một công cụ mạnh mẽ để quản lý các môi trường khác nhau cho từng dự án và tự động hóa các tác vụ lặp lại. Trong bài viết này, chúng ta sẽ tìm hiểu cách cài đặt Autoenv trên các nền tảng khác nhau và một số ví dụ sử dụng thực tế.\u003c/p\u003e","title":"Sử dụng Autoenv: Con đường tắt tối thượng để quản lý môi trường"},{"content":"I. Tổng quan Node Version Manager (NVM) là một công cụ hữu ích để quản lý và chuyển đổi giữa nhiều phiên bản Node.js. Trong bài viết này, chúng ta sẽ tìm hiểu các tính năng được sử dụng phổ biến nhất của NVM, hướng dẫn cài đặt trên các nền tảng khác nhau, và lý do tại sao đây là một công cụ đáng giá cho các nhà phát triển.\nII. Cài đặt macOS Sử dụng Homebrew:\nbrew install nvm mkdir ~/.nvm Thêm các dòng sau vào file cấu hình shell .bash_profile, .zshrc hoặc file tương tự:\nexport NVM_DIR=\u0026#34;$HOME/.nvm\u0026#34; [ -s \u0026#34;$(brew --prefix)/opt/nvm/nvm.sh\u0026#34; ] \u0026amp;\u0026amp; . \u0026#34;$(brew --prefix)/opt/nvm/nvm.sh\u0026#34; # This loads nvm [ -s \u0026#34;$(brew --prefix)/opt/nvm/etc/bash_completion\u0026#34; ] \u0026amp;\u0026amp; . \u0026#34;$(brew --prefix)/opt/nvm/etc/bash_completion\u0026#34; # This loads nvm bash_completion Linux và các hệ thống dựa trên Unix khác Sử dụng curl:\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash Hoặc sử dụng wget:\nwget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash Script này sẽ clone repository NVM vào ~/.nvm và thêm các dòng cần thiết vào file cấu hình shell của bạn (.bashrc, .zshrc, v.v.).\nIII. Cách sử dụng 1. Liệt kê các phiên bản Node.js có sẵn Để xem danh sách các phiên bản Node.js có sẵn, chạy lệnh:\nnvm ls-remote 2. Cài đặt một phiên bản Node.js cụ thể Để cài đặt một phiên bản cụ thể, sử dụng lệnh nvm install kèm theo số phiên bản:\nnvm install 14.17.0 3. Liệt kê các phiên bản Node.js đã cài đặt Để xem danh sách các phiên bản Node.js đã cài đặt, chạy lệnh:\nnvm ls 4. Chuyển đổi giữa các phiên bản Node.js Để chuyển sang một phiên bản Node.js cụ thể, sử dụng lệnh nvm use kèm theo số phiên bản:\nnvm use 14.17.0 5. Đặt phiên bản Node.js mặc định Để đặt phiên bản mặc định cho các phiên shell mới, sử dụng lệnh nvm alias:\nnvm alias default 14.17.0 6. Gỡ cài đặt một phiên bản Node.js Để gỡ cài đặt một phiên bản Node.js cụ thể, sử dụng lệnh nvm uninstall kèm theo số phiên bản:\nnvm uninstall 14.17.0 7. Cài đặt phiên bản LTS (Hỗ trợ Dài hạn) mới nhất Để cài đặt phiên bản LTS mới nhất, chạy lệnh:\nnvm install --lts 8. Cập nhật một phiên bản Node.js đã cài đặt Để cập nhật một phiên bản đã cài đặt lên bản vá mới nhất, sử dụng lệnh nvm reinstall-packages:\nnvm install 14.17.0 --reinstall-packages-from=14.16.0 9. Chạy script với một phiên bản Node.js cụ thể Để chạy script bằng một phiên bản Node.js cụ thể mà không chuyển đổi phiên bản đang hoạt động, sử dụng lệnh nvm exec:\nnvm exec 14.17.0 node script.js 10. Chạy lệnh với một phiên bản Node.js cụ thể Để chạy lệnh bằng một phiên bản Node.js cụ thể mà không chuyển đổi phiên bản đang hoạt động, sử dụng lệnh nvm run:\nnvm run 14.17.0 --version IV. Kết luận NVM là một công cụ mạnh mẽ giúp các nhà phát triển quản lý nhiều phiên bản Node.js một cách dễ dàng. Nó cho phép chuyển đổi giữa các phiên bản Node.js một cách thuận tiện, giúp việc kiểm thử ứng dụng trên các môi trường khác nhau hoặc làm việc trên nhiều dự án với các yêu cầu Node.js khác nhau trở nên đơn giản hơn.\nVới các tính năng được sử dụng phổ biến nhất đã được đề cập trong bài viết này, bây giờ bạn đã có thể cài đặt NVM trên hệ thống của mình, quản lý các phiên bản Node.js và sử dụng công cụ này hiệu quả. Chúc bạn lập trình vui vẻ!\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-18-managing-multiple-nodejs-environments-with-nvm/","summary":"\u003ch1 id=\"i-tổng-quan\"\u003eI. Tổng quan\u003c/h1\u003e\n\u003cp\u003eNode Version Manager (NVM) là một công cụ hữu ích để quản lý và chuyển đổi giữa nhiều phiên bản Node.js. Trong bài viết này, chúng ta sẽ tìm hiểu các tính năng được sử dụng phổ biến nhất của NVM, hướng dẫn cài đặt trên các nền tảng khác nhau, và lý do tại sao đây là một công cụ đáng giá cho các nhà phát triển.\u003c/p\u003e","title":"Quản lý nhiều môi trường Node.js với NVM"},{"content":"Java là ngôn ngữ lập trình cực kỳ phổ biến, và việc quản lý nhiều phiên bản Java có thể là một thách thức. Đó là lúc jEnv phát huy tác dụng. Trong bài viết này, chúng ta sẽ đề cập đến các tính năng được dùng nhiều nhất của jEnv, bao gồm hướng dẫn cài đặt cho các nền tảng khác nhau, và giúp bạn quản lý các phiên bản Java một cách dễ dàng.\nI. Tổng quan jEnv là công cụ dòng lệnh cho phép bạn quản lý nhiều phiên bản Java trên hệ thống. Nó cho phép bạn đặt phiên bản Java mong muốn theo từng dự án, theo phiên shell hoặc trên toàn hệ thống. Bằng cách dùng jEnv, bạn có thể tránh xung đột giữa các dự án khác nhau yêu cầu các phiên bản Java khác nhau và đảm bảo trải nghiệm phát triển mượt mà hơn.\nII. Cài đặt Mac Để cài đặt jEnv trên macOS, bạn có thể dùng trình quản lý gói Homebrew. Nếu chưa cài đặt Homebrew, bạn có thể tìm hướng dẫn cài đặt tại đây.\nbrew install jenv Sau khi cài đặt, thêm các dòng sau vào file ~/.bash_profile, ~/.zshrc hoặc ~/.bashrc, tùy thuộc vào shell bạn đang dùng:\nexport PATH=\u0026#34;$HOME/.jenv/bin:$PATH\u0026#34; eval \u0026#34;$(jenv init -)\u0026#34; Linux Để cài đặt jEnv trên Linux, hãy chạy các lệnh sau:\ngit clone https://github.com/jenv/jenv.git ~/.jenv Sau khi clone kho lưu trữ, thêm các dòng sau vào file ~/.bashrc hoặc ~/.zshrc, tùy thuộc vào shell bạn đang dùng:\nexport PATH=\u0026#34;$HOME/.jenv/bin:$PATH\u0026#34; eval \u0026#34;$(jenv init -)\u0026#34; III. Cách sử dụng local Lệnh local đặt phiên bản Java cho một thư mục cụ thể. Tính năng này hữu ích khi làm việc với nhiều dự án có yêu cầu phiên bản Java khác nhau.\njenv local 11.0.2 global Lệnh global đặt phiên bản Java mặc định cho toàn bộ hệ thống. Phiên bản này sẽ được dùng nếu không có phiên bản nào khác được chỉ định bởi lệnh local hoặc shell.\njenv global 11.0.2 shell Lệnh shell đặt phiên bản Java cho phiên shell hiện tại. Tính năng này hữu ích khi bạn muốn tạm thời dùng một phiên bản Java khác mà không ảnh hưởng đến các dự án hoặc phiên khác.\njenv shell 11.0.2 rehash Lệnh rehash tạo shim cho tất cả các file thực thi Java mà jEnv biết đến. Tính năng này hữu ích sau khi cài đặt phiên bản Java mới hoặc khi jEnv không nhận ra một số file thực thi Java.\njenv rehash version Lệnh version hiển thị phiên bản Java đang hoạt động hiện tại.\njenv version versions Lệnh versions liệt kê tất cả các phiên bản Java đã cài đặt và đánh dấu phiên bản đang hoạt động bằng dấu hoa thị.\njenv versions which Lệnh which hiển thị đường dẫn đến file thực thi Java của phiên bản đang hoạt động.\njenv which java whence Lệnh whence liệt kê tất cả các phiên bản Java chứa một lệnh cụ thể.\njenv whence java add Lệnh add đăng ký phiên bản Java mới với jEnv. Bạn cần cung cấp đường dẫn đến thư mục cài đặt Java.\njenv add /path/to/java/home IV. Kết luận jEnv là công cụ mạnh mẽ giúp đơn giản hóa việc quản lý phiên bản Java. Với các tính năng như local, global và shell, bạn có thể dễ dàng chuyển đổi giữa các phiên bản Java theo từng dự án hoặc theo từng phiên. Các lệnh rehash, version, versions, which, whence và add tiếp tục nâng cao khả năng quản lý và tổ chức các bản cài đặt Java của bạn. Bằng cách dùng jEnv, bạn có thể tối ưu hóa quy trình phát triển, tránh xung đột phiên bản và đảm bảo các dự án chạy trên phiên bản Java mong muốn. Dù bạn đang làm việc với nhiều dự án Java hay chỉ đơn giản muốn kiểm soát tốt hơn môi trường Java, jEnv là công cụ không thể thiếu đối với các nhà phát triển Java.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-17-a-practical-guide-to-using-jenv-for-java-version-management/","summary":"\u003cp\u003eJava là ngôn ngữ lập trình cực kỳ phổ biến, và việc quản lý nhiều phiên bản Java có thể là một thách thức. Đó là lúc jEnv phát huy tác dụng. Trong bài viết này, chúng ta sẽ đề cập đến các tính năng được dùng nhiều nhất của jEnv, bao gồm hướng dẫn cài đặt cho các nền tảng khác nhau, và giúp bạn quản lý các phiên bản Java một cách dễ dàng.\u003c/p\u003e","title":"Hướng dẫn thực tế sử dụng jEnv để quản lý phiên bản Java"},{"content":"Trong bài viết này, chúng ta sẽ thảo luận về cách sử dụng pyenv và pyenv-virtualenv — hai công cụ mạnh mẽ giúp bạn quản lý nhiều phiên bản Python và môi trường ảo một cách dễ dàng. Chúng ta sẽ đề cập đến hướng dẫn cài đặt cho các nền tảng như Mac và Linux, đồng thời thảo luận về các tính năng được dùng thường xuyên nhất của các công cụ này. Đến cuối bài, bạn sẽ hiểu vững cách sử dụng các công cụ này hiệu quả trong quy trình phát triển.\nI. Tổng quan pyenv là công cụ quản lý phiên bản mạnh mẽ cho Python, cho phép bạn cài đặt và chuyển đổi giữa nhiều phiên bản Python một cách dễ dàng. pyenv-virtualenv là phần mở rộng của pyenv cho phép bạn quản lý nhiều môi trường ảo. Các công cụ này đặc biệt hữu ích khi làm việc với nhiều dự án có dependency và phiên bản Python khác nhau.\nII. Cài đặt Mac Để cài đặt pyenv và pyenv-virtualenv trên macOS, bạn có thể dùng Homebrew:\nbrew update brew install pyenv brew install pyenv-virtualenv Sau khi cài đặt, thêm các dòng sau vào file cấu hình shell của bạn (.bashrc, .zshrc, v.v.):\nif command -v pyenv 1\u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then eval \u0026#34;$(pyenv init -)\u0026#34; fi if command -v pyenv-virtualenv-init 1\u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then eval \u0026#34;$(pyenv virtualenv-init -)\u0026#34; fi Linux Để cài đặt pyenv và pyenv-virtualenv trên Linux, trước tiên clone các kho lưu trữ và thêm vào PATH:\ngit clone https://github.com/pyenv/pyenv.git ~/.pyenv git clone https://github.com/pyenv/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv Tiếp theo, thêm các dòng sau vào file cấu hình shell của bạn (.bashrc, .zshrc, v.v.):\nexport PYENV_ROOT=\u0026#34;$HOME/.pyenv\u0026#34; export PATH=\u0026#34;$PYENV_ROOT/bin:$PATH\u0026#34; eval \u0026#34;$(pyenv init -)\u0026#34; eval \u0026#34;$(pyenv virtualenv-init -)\u0026#34; III. Cách sử dụng 1. Cài đặt phiên bản Python Để cài đặt một phiên bản Python cụ thể, dùng lệnh install:\npyenv install 3.9.5 2. Liệt kê các phiên bản Python có sẵn Để xem tất cả các phiên bản Python đã cài đặt, dùng lệnh versions:\npyenv versions 3. Đặt phiên bản Python toàn cục Để đặt phiên bản Python toàn cục, dùng lệnh global:\npyenv global 3.9.5 4. Đặt phiên bản Python cục bộ Để đặt phiên bản Python cục bộ cho một dự án cụ thể, dùng lệnh local trong thư mục dự án:\npyenv local 3.8.10 5. Kiểm tra phiên bản Python hiện tại Để kiểm tra phiên bản Python hiện tại, dùng lệnh version:\npyenv version 6. Tạo môi trường ảo Để tạo môi trường ảo mới với pyenv-virtualenv, dùng lệnh virtualenv:\npyenv virtualenv 3.9.5 my-project-env 7. Kích hoạt môi trường ảo Để kích hoạt một môi trường ảo, dùng lệnh activate:\npyenv activate my-project-env 8. Hủy kích hoạt môi trường ảo Để hủy kích hoạt môi trường ảo hiện tại, dùng lệnh deactivate:\npyenv deactivate 9. Liệt kê các môi trường ảo có sẵn Để liệt kê tất cả các môi trường ảo bạn đã tạo, dùng lệnh virtualenvs:\npyenv virtualenvs 10. Xóa môi trường ảo Để xóa một môi trường ảo, dùng lệnh uninstall:\npyenv uninstall my-project-env Bonus: Rehash Mỗi khi bạn cài đặt một gói Python mới có script thực thi, điều cần thiết là chạy lệnh rehash để cập nhật các shim, đảm bảo các script mới có thể được sử dụng:\npyenv rehash IV. Kết luận pyenv và pyenv-virtualenv là các công cụ vô giá để quản lý nhiều phiên bản Python và môi trường ảo trong quy trình phát triển. Với các tính năng được thảo luận trong hướng dẫn này, bạn sẽ được trang bị tốt để làm việc trên nhiều dự án với dependency và phiên bản Python khác nhau. Hãy tận dụng các công cụ này để duy trì môi trường phát triển sạch và có tổ chức, nâng cao năng suất và giảm rủi ro xung đột dependency.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-16-how-to-use-pyenv-and-pyenv-virtualenv/","summary":"\u003cp\u003eTrong bài viết này, chúng ta sẽ thảo luận về cách sử dụng \u003ccode\u003epyenv\u003c/code\u003e và \u003ccode\u003epyenv-virtualenv\u003c/code\u003e — hai công cụ mạnh mẽ giúp bạn quản lý nhiều phiên bản Python và môi trường ảo một cách dễ dàng. Chúng ta sẽ đề cập đến hướng dẫn cài đặt cho các nền tảng như Mac và Linux, đồng thời thảo luận về các tính năng được dùng thường xuyên nhất của các công cụ này. Đến cuối bài, bạn sẽ hiểu vững cách sử dụng các công cụ này hiệu quả trong quy trình phát triển.\u003c/p\u003e","title":"Cách sử dụng Pyenv và Pyenv-Virtualenv"},{"content":"Các nhà phát triển Ruby thường phải làm việc với nhiều dự án, mỗi dự án có yêu cầu phiên bản riêng. Vì vậy, nhu cầu về một công cụ quản lý phiên bản linh hoạt và dễ sử dụng là rất quan trọng. Trong bài viết này, chúng ta sẽ thảo luận về rbenv — trình quản lý môi trường Ruby phổ biến cung cấp giải pháp tinh tế cho vấn đề này. Chúng ta sẽ xem qua các tính năng được dùng nhiều nhất, hướng dẫn cài đặt cho các nền tảng khác nhau và kết thúc bằng phần kết luận.\nI. Tổng quan rbenv là công cụ quản lý phiên bản Ruby nhẹ, cho phép bạn chuyển đổi giữa các phiên bản Ruby khác nhau theo từng dự án hoặc trên toàn hệ thống. Với rbenv, bạn có thể dễ dàng cài đặt các phiên bản Ruby mới, giữ chúng cập nhật và duy trì các gem set riêng biệt cho mỗi phiên bản.\nMột số tính năng được dùng nhiều nhất của rbenv bao gồm:\nCài đặt phiên bản Ruby Đặt phiên bản Ruby toàn cục Đặt phiên bản Ruby cục bộ (theo dự án) Liệt kê các phiên bản Ruby đã cài đặt Gỡ bỏ phiên bản Ruby II. Cài đặt Mac Để cài đặt rbenv trên macOS, bạn có thể dùng Homebrew:\nbrew install rbenv Sau khi cài đặt, thêm rbenv vào bash để nó tải mỗi khi bạn mở Terminal:\necho \u0026#39;if which rbenv \u0026gt; /dev/null; then eval \u0026#34;$(rbenv init -)\u0026#34;; fi\u0026#39; \u0026gt;\u0026gt; ~/.zshrc source ~/.zshrc Linux Để cài đặt rbenv trên hệ thống Linux, hãy làm theo các bước sau:\nCập nhật danh sách gói: sudo apt-get update Cài đặt các dependency: sudo apt-get install -y build-essential libssl-dev libreadline-dev zlib1g-dev Clone rbenv từ kho GitHub: git clone https://github.com/rbenv/rbenv.git ~/.rbenv Thêm rbenv vào PATH: echo \u0026#39;export PATH=\u0026#34;$HOME/.rbenv/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc Thêm lệnh khởi tạo rbenv vào shell: echo \u0026#39;eval \u0026#34;$(rbenv init -)\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc Khởi động lại shell: exec $SHELL III. Cách sử dụng 1. Cài đặt phiên bản Ruby Để cài đặt một phiên bản Ruby cụ thể, trước tiên hãy cài đặt plugin ruby-build:\nbrew install ruby-build Bây giờ bạn có thể cài đặt phiên bản Ruby mong muốn:\nrbenv install 2.7.0 2. Đặt phiên bản Ruby toàn cục Để đặt phiên bản Ruby toàn cục cho hệ thống, dùng lệnh global:\nrbenv global 2.7.0 3. Đặt phiên bản Ruby cục bộ (theo dự án) Để đặt phiên bản Ruby cho một dự án cụ thể, điều hướng đến thư mục dự án và dùng lệnh local:\ncd /path/to/your/project rbenv local 2.7.0 4. Liệt kê các phiên bản Ruby đã cài đặt Để liệt kê tất cả các phiên bản Ruby đã cài đặt, dùng lệnh versions:\nrbenv versions 5. Gỡ bỏ phiên bản Ruby Để gỡ bỏ một phiên bản Ruby đã cài đặt, dùng lệnh uninstall:\nrbenv uninstall 2.7.0 IV. Kết luận Tóm lại, rbenv là công cụ không thể thiếu đối với các nhà phát triển Ruby cần quản lý nhiều môi trường Ruby. Nó cung cấp cách đơn giản nhưng mạnh mẽ để chuyển đổi giữa các phiên bản Ruby, quản lý gemset và đảm bảo các dependency theo từng dự án được duy trì. Với khả năng cài đặt dễ dàng và tương thích đa nền tảng, rbenv là công cụ bắt buộc có đối với bất kỳ Rubyist nào muốn tối ưu hóa quy trình phát triển và giữ dự án ngăn nắp. Hãy thử rbenv và bạn sẽ sớm tự hỏi mình đã làm việc như thế nào khi chưa có công cụ này.\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-15-managing-multiple-ruby-environments-with-rbenv/","summary":"\u003cp\u003eCác nhà phát triển Ruby thường phải làm việc với nhiều dự án, mỗi dự án có yêu cầu phiên bản riêng. Vì vậy, nhu cầu về một công cụ quản lý phiên bản linh hoạt và dễ sử dụng là rất quan trọng. Trong bài viết này, chúng ta sẽ thảo luận về rbenv — trình quản lý môi trường Ruby phổ biến cung cấp giải pháp tinh tế cho vấn đề này. Chúng ta sẽ xem qua các tính năng được dùng nhiều nhất, hướng dẫn cài đặt cho các nền tảng khác nhau và kết thúc bằng phần kết luận.\u003c/p\u003e","title":"Quản lý nhiều môi trường Ruby với rbenv"},{"content":"I. Giới thiệu Báo cáo phát triển này nhằm trình bày cách sử dụng Notion API để tự động hóa lịch tập luyện. Notion là công cụ năng suất toàn diện cho phép người dùng tạo và tổ chức cơ sở dữ liệu, ghi chú, tác vụ và nhiều hơn nữa. Notion API cung cấp cho các nhà phát triển khả năng truy cập lập trình vào các tính năng của Notion, cho phép tự động hóa nhiều tác vụ khác nhau.\nĐoạn code được cung cấp minh họa cách sử dụng Notion API để tạo các sự kiện tập luyện trong cơ sở dữ liệu dựa trên lịch trình được định sẵn. Script sẽ lấy các sự kiện hiện có trong một khoảng ngày cụ thể và tạo sự kiện mới cho những ngày chưa có lịch. Lịch tập luyện được định nghĩa trong từ điển days_of_week, ánh xạ mỗi ngày trong tuần tới tên bài tập và danh sách thẻ.\nBằng cách tự động hóa quy trình lập lịch tập luyện với Notion API, các nhà phát triển có thể tiết kiệm thời gian và giảm thiểu lỗi thủ công. Báo cáo này sẽ cung cấp hướng dẫn từng bước giúp bạn thiết lập Notion API và hiểu các thành phần khác nhau của đoạn code.\nII. Thiết lập Notion API Để sử dụng Notion API, bạn cần có tài khoản Notion và một integration đã được thiết lập. Dưới đây là hướng dẫn từng bước để xác thực với Notion API:\nTạo integration trong Notion\nĐăng nhập vào Notion và điều hướng đến trang Integrations bằng cách nhấp vào biểu tượng hồ sơ ở góc dưới bên trái màn hình, sau đó nhấp vào \u0026ldquo;Integrations\u0026rdquo;. Nhấp vào nút \u0026ldquo;Create a new integration\u0026rdquo;. Đặt tên cho integration và chọn workspace bạn muốn sử dụng. Nhấp vào nút \u0026ldquo;Submit\u0026rdquo; để tạo integration. Lấy token integration\nSau khi integration được tạo, bạn có thể lấy token bằng cách nhấp vào tên integration. Sao chép token integration để dùng trong code của bạn. Cài đặt gói Notion API\nMở terminal hoặc command prompt.\nChạy lệnh sau để cài đặt gói Notion API:\npip install notion-client Thiết lập Notion API client\nNhập Notion API client vào code bằng cách thêm dòng sau ở đầu file:\nfrom notion_client import Client Xác thực client bằng cách thêm dòng sau và thay \u0026lt;API Key\u0026gt; bằng token integration của bạn:\nnotion = Client(auth=\u0026#34;\u0026lt;API Key\u0026gt;\u0026#34;) Thiết lập thông tin cơ sở dữ liệu\nLấy ID của cơ sở dữ liệu nơi bạn muốn thêm sự kiện. Thay biến database_id trong đoạn code bằng ID cơ sở dữ liệu của bạn. Đoạn code sử dụng đối tượng notion_client.Client để tương tác với Notion API. Đối tượng Client dùng để xác thực API client và cung cấp quyền truy cập vào các tài nguyên Notion khác nhau như cơ sở dữ liệu và trang.\nBiến database_id được đặt thành ID của cơ sở dữ liệu Notion nơi các sự kiện tập luyện sẽ được thêm vào. ID có thể tìm thấy trong URL của cơ sở dữ liệu.\nIII. Định nghĩa lịch tập luyện Lịch tập luyện được định nghĩa trong đoạn code bằng các biến sau:\nstart_days_ago: số ngày trước ngày hôm nay để bắt đầu lịch end_days_after: số ngày sau ngày hôm nay để kết thúc lịch days_of_week: từ điển ánh xạ mỗi ngày trong tuần tới tên bài tập và danh sách thẻ. Từ điển days_of_week dùng để định nghĩa kế hoạch tập luyện cho từng ngày trong tuần. Mỗi ngày là một khóa trong từ điển, giá trị tương ứng là một từ điển khác chứa tên bài tập và danh sách thẻ liên quan. Ví dụ:\ndays_of_week = { \u0026#34;Monday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 1\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;chest\u0026#34;, \u0026#34;triceps\u0026#34;]}, \u0026#34;Tuesday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 2\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;back\u0026#34;, \u0026#34;biceps\u0026#34;]}, \u0026#34;Wednesday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 3\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;lower body\u0026#34;, \u0026#34;shoulder\u0026#34;]}, \u0026#34;Thursday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 1\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;chest\u0026#34;, \u0026#34;triceps\u0026#34;]}, \u0026#34;Friday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 2\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;back\u0026#34;, \u0026#34;biceps\u0026#34;]}, \u0026#34;Saturday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 3\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;lower body\u0026#34;, \u0026#34;shoulder\u0026#34;]}, } Trong ví dụ này, lịch được chia thành ba ngày, mỗi ngày có bài tập cụ thể và các thẻ liên quan.\nCác biến start_days_ago và end_days_after dùng để định nghĩa khoảng ngày cho lịch tập luyện. start_days_ago chỉ định bao nhiêu ngày trước hôm nay để bắt đầu lịch, và end_days_after chỉ định bao nhiêu ngày sau hôm nay để kết thúc lịch.\nĐoạn code sử dụng module datetime để lấy ngày hôm nay và tính toán ngày bắt đầu và kết thúc của lịch tập luyện. Ngày bắt đầu được tính bằng cách trừ start_days_ago từ ngày hôm nay, ngày kết thúc được tính bằng cách cộng end_days_after vào ngày hôm nay. Các ngày được định dạng theo chuỗi %Y-%m-%d.\nIV. Truy vấn sự kiện hiện có Trước khi tạo sự kiện tập luyện mới, đoạn code lấy các sự kiện hiện có trong khoảng ngày đã định. Điều này nhằm tránh tạo sự kiện trùng lặp trên cùng một ngày.\nPhương thức notion.databases.query() dùng để truy vấn cơ sở dữ liệu Notion và lấy các sự kiện hiện có. Tham số filter dùng để chỉ định điều kiện lọc cho truy vấn. Trong trường hợp này, bộ lọc được đặt để lấy các sự kiện nằm trong khoảng ngày bắt đầu và kết thúc đã định. Dưới đây là ví dụ về bộ lọc được dùng trong truy vấn:\n{ \u0026#34;and\u0026#34;: [ {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_after\u0026#34;: start_date}}, {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_before\u0026#34;: end_date}} ] } Bộ lọc này trả về các sự kiện có thuộc tính Date nằm giữa start_date và end_date.\nThuộc tính results của phản hồi truy vấn dùng để lấy các sự kiện hiện có trong khoảng ngày. Kết quả được lưu vào biến existing_events để dùng sau trong đoạn code.\nexisting_events = notion.databases.query( **{ \u0026#34;database_id\u0026#34;: database_id, \u0026#34;filter\u0026#34;: { \u0026#34;and\u0026#34;: [ {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_after\u0026#34;: start_date}}, {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_before\u0026#34;: end_date}} ] }, } ).get(\u0026#34;results\u0026#34;) V. Tạo sự kiện mới Sau khi đã lấy được các sự kiện hiện có trong khoảng ngày, đoạn code tiến hành tạo sự kiện tập luyện mới cho từng ngày trong khoảng ngày của lịch.\nĐoạn code sử dụng vòng lặp for để duyệt qua từng ngày trong khoảng. Sự kiện mới được tạo cho mỗi ngày không phải Chủ nhật:\nfor date in (datetime.now() + timedelta(days=n) for n in range(-start_days_ago, end_days_after + 1)): # skip Sundays if date.strftime(\u0026#34;%A\u0026#34;) == \u0026#34;Sunday\u0026#34;: continue Phương thức strftime() dùng để lấy tên ngày trong tuần từ ngày, và nếu tên ngày là \u0026ldquo;Sunday\u0026rdquo;, vòng lặp bỏ qua ngày đó và chuyển sang ngày tiếp theo.\nTiếp theo, đoạn code kiểm tra xem đã có sự kiện nào vào ngày hiện tại chưa:\nif any(event.get(\u0026#34;properties\u0026#34;).get(\u0026#34;Date\u0026#34;).get(\u0026#34;date\u0026#34;).get(\u0026#34;start\u0026#34;) == date.strftime(\u0026#34;%Y-%m-%d\u0026#34;) for event in existing_events): print(f\u0026#34;Event already exists for {date.strftime(\u0026#39;%Y-%m-%d\u0026#39;)}. Skipping.\u0026#34;) continue Nếu đã có sự kiện vào ngày hiện tại, vòng lặp bỏ qua ngày đó và chuyển sang ngày tiếp theo.\nNếu không có sự kiện nào vào ngày hiện tại, sự kiện mới được tạo với tên bài tập và thẻ liên quan đến ngày đó trong tuần:\nday_of_week = date.strftime(\u0026#34;%A\u0026#34;) event_name = days_of_week[day_of_week][\u0026#34;name\u0026#34;] event_tag = days_of_week[day_of_week][\u0026#34;tag\u0026#34;] new_event = { \u0026#34;Name\u0026#34;: {\u0026#34;title\u0026#34;: [{\u0026#34;text\u0026#34;: {\u0026#34;content\u0026#34;: event_name}}]}, \u0026#34;Date\u0026#34;: {\u0026#34;date\u0026#34;: {\u0026#34;start\u0026#34;: date.strftime(\u0026#34;%Y-%m-%d\u0026#34;)}}, \u0026#34;Tag\u0026#34;: {\u0026#34;multi_select\u0026#34;: [{\u0026#34;name\u0026#34;: tag} for tag in event_tag]}, } notion.pages.create(parent={\u0026#34;database_id\u0026#34;: database_id}, properties=new_event) Tên bài tập và thẻ liên quan đến ngày trong tuần được lấy từ từ điển days_of_week. Từ điển new_event dùng để định nghĩa các thuộc tính của sự kiện mới, bao gồm tên sự kiện, ngày và thẻ. Phương thức notion.pages.create() dùng để tạo sự kiện mới trong cơ sở dữ liệu Notion.\nBằng cách tạo sự kiện mới cho từng ngày trong tuần trong khoảng ngày của lịch, các nhà phát triển có thể tự động hóa quy trình lập lịch tập luyện và đảm bảo kế hoạch luôn được cập nhật và chính xác.\nVI. Kết luận Trong dự án này, chúng ta đã trình bày cách sử dụng Notion API để tự động hóa quy trình tạo lịch tập luyện trong cơ sở dữ liệu Notion. Đoạn code được cung cấp có thể dùng để tạo lịch tập luyện cho một khoảng ngày cụ thể và tự động thêm sự kiện mới vào cơ sở dữ liệu dựa trên kế hoạch tập luyện đã định.\nNotion API có thể dùng để tự động hóa nhiều loại tác vụ, chẳng hạn như quản lý danh sách việc cần làm, theo dõi chi phí hoặc sắp xếp tác vụ dự án. Với Notion API, các nhà phát triển có thể xây dựng tích hợp tùy chỉnh và tự động hóa các tác vụ lặp đi lặp lại, tiết kiệm thời gian và tăng năng suất.\nNhìn chung, Notion API cung cấp công cụ mạnh mẽ cho các nhà phát triển để tích hợp Notion vào quy trình làm việc và tự động hóa các tác vụ. Khi Notion ngày càng phổ biến, chúng ta có thể kỳ vọng thấy nhiều nhà phát triển hơn xây dựng tích hợp và tự động hóa tác vụ với Notion API.\nTrong tương lai, chúng tôi dự định mở rộng dự án này bằng cách thêm nhiều tính năng hơn và tích hợp với các công cụ khác. Chúng tôi cũng dự định khám phá các trường hợp sử dụng khác của Notion API và tìm hiểu cách nó có thể tự động hóa các tác vụ khác trong quy trình làm việc.\nSource code : https://github.com/hobbyworker/notion-workout-scheduler\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-14-automating-workout-scheduling-with-notion-api/","summary":"\u003ch1 id=\"i-giới-thiệu\"\u003e\u003cstrong\u003eI. Giới thiệu\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003eBáo cáo phát triển này nhằm trình bày cách sử dụng Notion API để tự động hóa lịch tập luyện. Notion là công cụ năng suất toàn diện cho phép người dùng tạo và tổ chức cơ sở dữ liệu, ghi chú, tác vụ và nhiều hơn nữa. Notion API cung cấp cho các nhà phát triển khả năng truy cập lập trình vào các tính năng của Notion, cho phép tự động hóa nhiều tác vụ khác nhau.\u003c/p\u003e","title":"Tự động hóa lịch tập luyện với Notion API"},{"content":"Tổng quan Homebrew là trình quản lý gói miễn phí, mã nguồn mở giúp đơn giản hóa việc cài đặt và quản lý phần mềm trên macOS. Với Homebrew, bạn có thể dễ dàng cài đặt, cập nhật và gỡ bỏ các gói phần mềm không có trên macOS App Store. Công cụ này được các nhà phát triển và người dùng nâng cao ưa chuộng nhờ sự đơn giản và dễ sử dụng.\nHướng dẫn cài đặt Trước khi cài đặt Homebrew, hãy đảm bảo bạn có các điều kiện sau:\nThiết bị macOS đã cài đặt phiên bản mới nhất của Xcode Command Line Tools Kết nối internet ổn định Để cài đặt Homebrew, mở Terminal trên Mac và chạy lệnh sau:\n/bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; Lệnh này tải xuống script cài đặt từ kho lưu trữ Homebrew và thực thi nó. Quá trình cài đặt có thể mất vài phút. Sau khi hoàn tất, bạn có thể xác nhận Homebrew đã được cài đặt bằng cách chạy:\nbrew --version Nếu bạn thấy số phiên bản hiển thị, xin chúc mừng! Bạn đã cài đặt Homebrew thành công trên thiết bị macOS của mình.\nCách sử dụng Homebrew Homebrew được xây dựng xung quanh khái niệm \u0026ldquo;formulae\u0026rdquo; — về cơ bản là các hướng dẫn để cài đặt gói phần mềm. Các formulae này được viết bằng Ruby và lưu trữ trong kho lưu trữ chính thức của Homebrew, gọi là \u0026ldquo;Homebrew/core\u0026rdquo;.\nDưới đây là một số lệnh Homebrew cơ bản để bắt đầu:\nTìm kiếm gói: Dùng brew search \u0026lt;package-name\u0026gt; để tìm kiếm các gói có sẵn. Thay \u0026lt;package-name\u0026gt; bằng tên gói bạn cần tìm. Cài đặt gói: Dùng brew install \u0026lt;package-name\u0026gt; để cài đặt gói mong muốn. Gỡ cài đặt gói: Dùng brew uninstall \u0026lt;package-name\u0026gt; để gỡ bỏ gói đã cài đặt. Liệt kê gói đã cài đặt: Dùng brew list để hiển thị danh sách tất cả các gói đã cài đặt. Cập nhật Homebrew: Dùng brew update để cập nhật Homebrew và các formulae lên phiên bản mới nhất. Nâng cấp gói đã cài đặt: Dùng brew upgrade để nâng cấp tất cả các gói đã cài đặt lên phiên bản mới nhất. Ví dụ: Cài đặt \u0026lsquo;wget\u0026rsquo; Trong ví dụ này, chúng ta sẽ cài đặt wget — một tiện ích dòng lệnh phổ biến để tải xuống tệp từ internet. Để thực hiện, chỉ cần chạy lệnh sau trong Terminal:\nbrew install wget Homebrew sẽ tải xuống và cài đặt gói wget. Sau khi cài đặt xong, bạn có thể dùng wget bằng cách gõ wget theo sau là các tùy chọn mong muốn và URL của tệp cần tải. Ví dụ, để tải xuống một tệp mẫu, hãy chạy:\nwget https://example.com/sample-file.txt Lệnh này sẽ tải xuống sample-file.txt từ URL đã chỉ định và lưu vào thư mục làm việc hiện tại của bạn.\nKết luận Homebrew là trình quản lý gói mạnh mẽ, dễ sử dụng, lấp đầy khoảng trống đáng kể trong khả năng quản lý phần mềm của macOS. Nó đơn giản hóa việc cài đặt, cập nhật và gỡ bỏ phần mềm và công cụ mã nguồn mở không có trên App Store. Với giao diện dòng lệnh trực quan và kho formulae phong phú, Homebrew đã trở thành công cụ không thể thiếu đối với người dùng macOS, đặc biệt là các nhà phát triển và người dùng nâng cao.\nTrong bài này, chúng ta đã tìm hiểu những điều cơ bản về Homebrew, bao gồm cách cài đặt, sử dụng và một ví dụ thực tế về cách cài đặt tiện ích wget. Bằng cách làm theo các hướng dẫn và lệnh được cung cấp, bạn có thể tự tin khám phá và cài đặt nhiều gói phần mềm để nâng cao trải nghiệm macOS của mình. Chúc bạn brew vui vẻ!\n","permalink":"https://hobbyworker.me/vi/dev/2023-03-13-a-beginners-guide-to-homebrew-the-missing-package-manager-for-macos/","summary":"\u003ch1 id=\"tổng-quan\"\u003eTổng quan\u003c/h1\u003e\n\u003cp\u003eHomebrew là trình quản lý gói miễn phí, mã nguồn mở giúp đơn giản hóa việc cài đặt và quản lý phần mềm trên macOS. Với Homebrew, bạn có thể dễ dàng cài đặt, cập nhật và gỡ bỏ các gói phần mềm không có trên macOS App Store. Công cụ này được các nhà phát triển và người dùng nâng cao ưa chuộng nhờ sự đơn giản và dễ sử dụng.\u003c/p\u003e","title":"Hướng dẫn Homebrew cho người mới bắt đầu: Trình quản lý gói cho macOS"},{"content":"","permalink":"https://hobbyworker.me/vi/projects/","summary":"","title":"Dự án"}]