🔍 搜尋結果:github

🔍 搜尋結果:github

為什麼事件溯源是微服務通訊的反模式

--- 標題:為什麼事件溯源是一種微服務通訊反模式 發表:真實 描述:這篇文章描述了合理的事件溯源用例,並提供了微服務之間通訊的替代方案。 封面圖片:https://github.com/OLibutzki/blog/raw/master/water-4230596\_1920.jpg 標籤: 事件溯源, 領域驅動設計, 領域事件 --- 一般而言,事件驅動架構,特別是事件溯源,在過去幾年中獲得了廣泛關注。這種趨勢是由於我們努力建構具有彈性和可擴展性的模組化系統而引起的。*微服務*是在這種情況下經常使用的術語。在我看來,微服務只是實現有界脈絡的一種方法。模組化系統的核心是模組的邊界,而如何辨識這些邊界最有前途的想法是 Eric Evans 的領域驅動設計中引入的[戰略設計](https://en.wikipedia.org/wiki/Domain-driven_design#Strategic_domain-driven_design)。它可以幫助您辨識/發現模組及其邊界([有界上下文](https://martinfowler.com/bliki/BoundedContext.html)),並描述這些有界上下文相互關聯的方式(上下文映射)。 *注意:我會預設某些術語的一些預知,因為我不想第一千次解釋它們。我決定連結到[microservices.io](https://microservices.io/) 、[維基百科](https://en.wikipedia.org)或[Martin Fowler 的 Bliki](https://martinfowler.com/bliki/)的解釋,因此,如果您對自己的知識感到不舒服,則可以更深入地研究某個主題。* 領域事件作為通用語言的核心 ============= 儘管在 Eric 的書中沒有明確提及,但領域事件很好地促進了 DDD 概念的發展。像 Alberto Brandolini 的[事件風暴](https://en.wikipedia.org/wiki/Event_storming)這樣的技術將事件的焦點從技術層面轉移到組織/業務層面。我們不討論一些 UI 層事件,例如*ButtonClickedEvent* ,而是討論領域事件,它們是業務領域的一部分,並由業務專家說出和理解。這些領域事件是一流的概念,並提供了一種形成所有參與者(領域專家、開發人員…)都同意的[通用語言的](https://martinfowler.com/bliki/UbiquitousLanguage.html)好方法。 用於跨界通訊的領域事件 =========== 領域事件可用於促進有界上下文之間的溝通。假設我們有一家具有三個限界上下文的線上商店:訂單、交貨、發票。 訂單上下文的網域事件是*訂單已接受*。發票和交付上下文對此事件的發生感興趣,因為它會導致一些內部流程啟動。 脫鉤神話 ==== 使用領域事件可以幫助您開發解耦的模組。模組可能會暫時離線。領域事件不關心不可用的模組,它們描述過去發生的事情。這取決於其他模組處理事件的速度。您得到的是一個設計上具有彈性的系統。 除了時間解耦之外,領域事件還具有另一個優勢,至少乍看之下是這樣: Order 上下文不必知道 Invoice 和 Delivery 上下文會偵聽其事件。實際上它甚至不需要知道這些上下文的存在。 這很酷,但具有挑戰性的部分是事件負載。哪些資料放入事件中? 簡單的答案:事件溯源! =========== 事件很有用,所以為什麼不賦予它們盡可能多的權力(和責任)。這就是[事件溯源](https://microservices.io/patterns/data/event-sourcing.html)的基本思想。您不會透過更新資料 (CRUD) 而是透過套用事件流來儲存聚合的狀態。 除了您可以重播事件以重建應用程式狀態之外,事件來源的一個重要功能是您可以免費獲得完整且可靠的審核日誌。因此,當需要這樣的稽核日誌時,在評估持久性策略時一定要考慮事件溯源。 事件溯源只是一種持久性策略嗎? =============== 您可能想知道為什麼我從領域事件直接轉向持久化策略,因為這些概念顯然適用於不同的層/抽象層級。 ……這就是我的觀點:事件溯源是由單一限界上下文做出的本地決策!這些事件不該向外界曝光!其他限界上下文則不知道彼此的持久化策略,因此它們不知道也不關心另一個限界上下文是否使用事件溯源。 如果您在全球範圍內使用事件溯源,則會暴露您的持久層。 您的持久性將成為您的公共 API。每次限界上下文調整其持久性資料時,我們都必須處理公共 API 變更。 我很確定每個人都同意,由於開發和執行時耦合,不同的限界上下文[共享(關係)資料庫中的資料](https://microservices.io/patterns/data/shared-database.html)是一個壞主意。但差別在哪裡呢? 空無一人。我們是否共享事件或資料庫表並不重要。在這兩種情況下,我們都會分享持久性詳細資訊。 有出路 === 我仍然認為領域事件非常適合限界上下文之間的通信,但這些事件不應與用於事件溯源的事件相對應。 我提出的解決方案是合乎邏輯的結果:無論您使用 CRUD 還是事件溯源方法來實現持久性,您都可以將領域事件發佈到全域事件儲存。這些領域事件是有界上下文的公共 API。如果您喜歡在限界上下文中使用事件溯源,則可以將這些事件儲存在本機事件儲存中,該儲存只能從此限界上下文存取 選擇的自由 ===== 在公共 API 中擁有專用領域事件可讓您決定如何對這些事件建模。您不受事件來源事件預先定義的佈局的限制。 對於每次發生的“現實世界事件”,您有兩個選擇: 使用已發布語言的開放主機服務 -------------- 準確發布一個網域事件,其中包含其他限界上下文可能需要的所有資料。在 DDD 術語中,我們將其稱為具有已發布語言的開放主機服務。 ![使用已發布語言的開放主機服務](https://raw.githubusercontent.com/OLibutzki/blog/master/OpenHostService.png) 現實世界事件*Order Accepted*的發生會導致發布一個網域事件*OrderAccepted* 。此事件的有效負載包含 Order 期望其他限界上下文感興趣的所有資料...因此希望 Invoice 和 Delivery 上下文找到它們所需的所有資訊。 客戶/供應商 ------ 發布多個專用領域事件,每個事件使用者一個。您必須與另一方(消費者)討論每個特定的領域事件,而不必定義共享模型。 DDD 將這種關係稱為客戶/供應商。 ![客戶/供應商](https://github.com/OLibutzki/blog/raw/master/CustomerSupplier.png) 現實世界事件*Order Accepted*的發生會導致每個消費限界上下文發布一個網域事件: *InvoiceOrderAccepted*和*DeliveryOrderAccepted* 。每個領域事件都包含消費上下文所請求的資料。 我不想討論這兩種選擇的優缺點。我只是想強調一下,您可以自由選擇領域事件的數量及其有效負載。 這是您不應低估的巨大優勢,因為您可以決定如何發展有界上下文的 API,而不必致力於事件溯源所需的事件。 結論 == 向外界公開持久性細節是一種眾所周知的反模式。在談論持久性時,我們首先想到的是資料庫表,但我解釋了為什麼用於事件來源的事件只是持久性資料的另一種方式。因此,暴露這些事件也是一種反模式。 {% 推特 784691906005635072 %} 如果以適當的(本地)方式使用事件溯源,它會非常強大。乍一看,它似乎是事件驅動架構的靈丹妙藥,但如果您深入研究,您會意識到它可能會引導您進入緊密耦合(分散式)系統……當然,這不是您的目標。 參考 == 除了我的個人經驗之外,我還從不同的摘要和會議演講中獲得了許多靈感。我想重點介紹 Eberhard Wolff 的演講《*基於事件的架構以及使用 Kafka 和 Atom 的實現》* 。特別是[事件溯源](https://youtu.be/Ecg7lvvm8aU?t=1178)和[事件中有什麼?](https://youtu.be/Ecg7lvvm8aU?t=655)與本博文的上下文高度相關。我選擇的線上商店範例是受到這次演講的啟發。 如果您想獲取更多訊息,還可以查閱其他一些資源: - [領域事件與事件溯源,](https://www.innoq.com/en/blog/domain-events-versus-event-sourcing/)作者:Christian Stettler,部落格文章 - [關於事件溯源,他們沒有告訴您的事情](https://medium.com/@hugo.oliveira.rocha/what-they-dont-tell-you-about-event-sourcing-6afc23c69e9a)由 Hugo Rocha 撰寫,部落格文章 - [DDD、CQRS、事件溯源的十年(CQRS/ES 不是頂級架構),](https://youtu.be/LDW0QWie21s?t=1259)作者:Greg Young,會議演講 --- 原文出處:https://dev.to/olibutzki/why-event-sourcing-is-a-microservice-anti-pattern-3mcj

使用外掛程式和主題在 OhMyZsh 和 Hyper 上設定自動完成功能的初學者指南!

您的普通 bash 可能具有您通常需要的功能,但**如果您是常規終端用戶,zsh 將改變您鍵入命令的方式。** zsh、ohmyzsh 和 hyper 一起提供的功能將讓您大吃一驚。 > 您知道您可以從終端控制 Spotify 嗎?是的,超級插件可以讓您做到這一點。 對於初學者來說,設定這些東西可能會讓人不知所措,所以這裡有一個非常簡單的入門指南! 🤩 --- 🔥 簡介 ---- 如果您使用的是如下所示的常規終端,則您會錯過 OhMyZsh 提供的許多功能。 ![Flaviocope 的 MacOS 終端](https://flaviocopes.com/macos-terminal-setup/Screenshot%202019-01-29%20at%2018.34.04.png) 今天,您將進行終端改造,使其看起來像這樣... ![我的超級終端](https://i.ibb.co/DW05RzF/Hyper-Terminal-Kumar-Abhirup.jpg) 不僅僅是外觀,OhMyZsh 還具有豐富的功能來點亮您的程式設計之旅。 在教程結束時,這就是您可以在終端機中執行的操作... - NPM、Git 自動完成 - 在終端機中輸入時自動建議 - 語法高亮顯示指令是否已定義 - 使用遊標編輯終端命令 - 查看目前目錄的`git branch`和`git status` - 開啟與目前分頁相同目錄的新分頁 - 使用 OhMyZsh 功能,例如不使用`cd`進行導航、使用`ll` 、更簡單的基於 Tab 鍵單擊的導航等等! --- ❤️ 開始吧 ------ 首先,您必須安裝`zsh` 。在某些情況下(取決於您正在執行的作業系統),它可能已經安裝。因此,請透過在終端機中執行`zsh --version`檢查它是否已安裝。 `zsh`在不同作業系統的安裝過程有所不同。查看[Zsh 安裝指南](https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH)來安裝 zsh。 安裝 Zsh 後,請確保將其設為預設 shell。為此,請在終端機中執行以下命令。 ``` $ sudo chsh -s $(which zsh) ``` 登出並登入回預設 shell。執行`echo $SHELL`並預期輸出`/bin/zsh`或類似內容。 --- 🔰 安裝 OhMyZsh ------------ > 請注意,zsh 和 OhMyZsh 是不同的。 透過在終端機中執行以下命令來安裝`OhMyZsh` 。 ``` $ sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" ``` 當您安裝 OhMyZsh 時,它會附帶許多插件來幫助您! 若要新增實用的插件,請在 TextEdit/Notepad/Vim/VSCode 中開啟`~/.zshrc` 。 在檔案中看到的插件清單中,只需新增一個名為`npm`的插件,如下所示 👇 ``` plugins=( git bundler dotenv osx rake rbenv ruby npm # you added this ) ``` 瞧!您已經完成了 OhMyZsh!若要查看更改,請在終端機中執行`source ~/.zshrc` ,現在您就擁有了 OhMyZsh shell 的功能。 --- 🔰 依時間安裝 HyperTerm ----------------- Zeit(now.sh 和 Next.js 的建立者)為我們建立了一個很棒的終端應用程式,它是用 Electron 建置的。 從[這裡](https://hyper.is/)下載 Hyper。 --- ### ⚛️ 使用 OhMyZsh 設定 Hyper 打開超級終端機。您不會看到 OhMyZsh 在那裡執行。因此,請轉到超級設定。在 OSX 上,它是`Hyper > Preferences` 。 這將在您最喜歡的編輯器中開啟一個`.hyper.js`檔案。該文件包含您的終端的所有設置,非常容易控制! 若要在 Hyper 中啟用 OhMyZsh 作為預設 shell,請在`.hyper.js`中進行此變更 👇 ``` - shell: '/bin/bash' + shell: '/bin/zsh' ``` **這將使 OhMyZsh 成為您的預設超級終端 shell!** --- ### 🤩 輸入指令時自動完成 Git 將`zsh-autocomplete`插件複製到 OhMyZsh 插件資料夾中。 ``` $ sudo git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions ``` 完成後,將外掛程式新增至`~/.zshrc`檔案的外掛程式清單中。 ``` plugins=( ... zsh-autosuggestions ) ``` --- ### 🎉 Zsh 語法高亮 Git 將`zsh-syntax-highlighting`外掛程式克隆到 OhMyZsh 外掛程式資料夾中。 ``` $ sudo git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting ``` 並再次將其新增至`.zshrc`檔案的外掛程式清單。 ``` plugins=( ... zsh-syntax-highlighting ) ``` > 注意:若要反映您所做的每項更改,請在終端機中執行`source ~/.zshrc` 。 --- ### 📯 啟用 Hyper 相關功能與主題 透過切換超級終端的設定來開啟`.hyper.js` 。 請查看`plugins: [...]`部分並將這些插件名稱貼到此處。 ``` plugins: [ ... 'hypercwd', 'hyper-statusline', 'hyper-alt-click', 'hyperterm-safepaste', 'hyper-search', 'hypergoogle', 'hyperborder', 'hyper-tab-icons', 'hyper-hide-title', 'shades-of-purple-hyper' ], ``` 儲存文件,Hyper 會自動為您安裝這些外掛程式和主題。要反映更改,只需關閉並再次啟動超級終端即可。 萬歲!**現在,您的終端機中已擁有本 DEV.to 文章開頭列出的所有功能。** --- 獎勵:在 VSCode 中為整合終端設定相同的終端配置 --------------------------- 在 VSCode 設定中,新增以下 JSON 鍵值對,然後就可以開始了! ``` { ... "terminal.integrated.shell.osx": "/bin/zsh", "terminal.integrated.fontSize": 16 } ``` **就是這樣,夥計們!** --- 🔥 資源 ---- - <https://ohmyz.sh> - <https://hyper.is> --- 🏆 關於我 ----- **我是 Kumar Abhirup,一位來自印度的 16 歲 JavaScript React 開發人員,每天都在學習新事物。** [在 Twitter 上與我聯絡 🐦](https://twitter.com/kumar_abhirup) [我的個人網站和作品集🖥️](https://kumar.now.sh) *請在下面評論您更好的方法以及改進本文的建議。 :)* --- 原文出處:https://dev.to/kumareth/a-beginner-s-guide-for-setting-up-autocomplete-on-ohmyzsh-hyper-with-plugins-themes-47f2

我正在建立一個全端應用程式:以下是我將要使用的庫......

您可以使用無數的框架和函式庫來改進您的全端應用程式。 我們將介紹令人興奮的概念,例如應用程式內通知、使用 React 製作影片、從為開發人員提供的電子郵件 API 到在瀏覽器中建立互動式音樂。 那我們就開始吧。 (不要忘記為這些庫加註星標以表示您的支持)。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqoipyuoxgb83swyoo4a.gif) https://github.com/CopilotKit/CopilotKit --- 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 在數小時內為您的產品提供 AI Copilot。 ------------------------------------------------------------------------------------ ![副駕駛套件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzuxjfog2ldam3csrl62.png) 您可以使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。它們還提供內建(完全可自訂)Copilot 原生 UX 元件,例如`<CopilotKit />` 、 `<CopilotPopup />` 、 `<CopilotSidebar />` 、 `<CopilotTextarea />` 。 開始使用以下 npm 指令。 ``` npm i @copilotkit/react-core @copilotkit/react-ui @copilotkit/react-textarea ``` 這是整合 CopilotTextArea 的方法。 ``` import { CopilotTextarea } from "@copilotkit/react-textarea"; import { useState } from "react"; export function SomeReactComponent() { const [text, setText] = useState(""); return ( <> <CopilotTextarea className="px-4 py-4" value={text} onValueChange={(value: string) => setText(value)} placeholder="What are your plans for your vacation?" autosuggestionsConfig={{ textareaPurpose: "Travel notes from the user's previous vacations. Likely written in a colloquial style, but adjust as needed.", chatApiConfigs: { suggestionsApiConfig: { forwardedParams: { max_tokens: 20, stop: [".", "?", "!"], }, }, }, }} /> </> ); } ``` 您可以閱讀[文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea)。 基本概念是在幾分鐘內建立可用於基於 LLM 的全端應用程式的 AI 聊天機器人。 https://github.com/CopilotKit/CopilotKit --- 2. [Storybook](https://github.com/storybookjs/storybook) - UI 開發、測試和文件變得簡單。 --------------------------------------------------------------------------- ![故事書](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/78rfum1ydisn51qhb408.png) Storybook 是一個用於獨立建立 UI 元件和頁面的前端工作坊。它有助於 UI 開發、測試和文件編制。 他們在 GitHub 上有超過 57,000 次提交、81,000 多個 star 和 1300 多個版本。 這是您為專案建立簡單元件的方法。 ``` import type { Meta, StoryObj } from '@storybook/react'; import { YourComponent } from './YourComponent'; //👇 This default export determines where your story goes in the story list const meta: Meta<typeof YourComponent> = { component: YourComponent, }; export default meta; type Story = StoryObj<typeof YourComponent>; export const FirstStory: Story = { args: { //👇 The args you need here will depend on your component }, }; ``` 您可以閱讀[文件](https://storybook.js.org/docs/get-started/setup)。 如今,UI 除錯起來很痛苦,因為它們與業務邏輯、互動狀態和應用程式上下文糾纏在一起。 Storybook 提供了一個獨立的 iframe 來渲染元件,而不會受到應用程式業務邏輯和上下文的干擾。這可以幫助您將開發重點放在元件的每個變體上,甚至是難以觸及的邊緣情況。 https://github.com/storybookjs/storybook --- 3. [Appwrite](https://github.com/appwrite/appwrite) - 您的後端減少麻煩。 --------------------------------------------------------------- ![應用程式寫入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8x568uz21seyygw6b72z.png) ![帶有 appwrite 的 sdk 列表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp7k8qnamsluto7eifpl.png) Appwrite 的開源平台可讓您將身份驗證、資料庫、函數和儲存體新增至您的產品中,並建立任何規模的任何應用程式、擁有您的資料並使用您喜歡的編碼語言和工具。 他們有很好的貢獻指南,甚至不厭其煩地詳細解釋架構。 開始使用以下 npm 指令。 ``` npm install appwrite ``` 您可以像這樣建立一個登入元件。 ``` "use client"; import { useState } from "react"; import { account, ID } from "./appwrite"; const LoginPage = () => { const [loggedInUser, setLoggedInUser] = useState(null); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [name, setName] = useState(""); const login = async (email, password) => { const session = await account.createEmailSession(email, password); setLoggedInUser(await account.get()); }; const register = async () => { await account.create(ID.unique(), email, password, name); login(email, password); }; const logout = async () => { await account.deleteSession("current"); setLoggedInUser(null); }; if (loggedInUser) { return ( <div> <p>Logged in as {loggedInUser.name}</p> <button type="button" onClick={logout}> Logout </button> </div> ); } return ( <div> <p>Not logged in</p> <form> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} /> <button type="button" onClick={() => login(email, password)}> Login </button> <button type="button" onClick={register}> Register </button> </form> </div> ); }; export default LoginPage; ``` 您可以閱讀[文件](https://appwrite.io/docs)。 Appwrite 可以非常輕鬆地建立具有開箱即用的擴充功能的可擴展後端應用程式。 https://github.com/appwrite/appwrite --- 4. [Wasp](https://github.com/wasp-lang/wasp) - 用於 React、node.js 和 prisma 的類似 Rails 的框架。 --------------------------------------------------------------------------------------- ![黃蜂](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi2mwazueoc3ezjx8a9q.png) 使用 React 和 Node.js 開發全端 Web 應用程式的最快方法。這不是一個想法,而是一種建立瘋狂快速全端應用程式的不同方法。 這是將其整合到元件中的方法。 ``` import getRecipes from "@wasp/queries/getRecipes"; import { useQuery } from "@wasp/queries"; import type { User } from "@wasp/entities"; export function HomePage({ user }: { user: User }) { // Due to full-stack type safety, `recipes` will be of type `Recipe[]` here. const { data: recipes, isLoading } = useQuery(getRecipes); // Calling our query here! if (isLoading) { return <div>Loading...</div>; } return ( <div> <h1>Recipes</h1> <ul> {recipes ? recipes.map((recipe) => ( <li key={recipe.id}> <div>{recipe.title}</div> <div>{recipe.description}</div> </li> )) : 'No recipes defined yet!'} </ul> </div> ); } ``` 您可以閱讀[文件](https://wasp-lang.dev/docs)。 https://github.com/wasp-lang/wasp --- 5. [Novu](https://github.com/novuhq/novu) - 將應用程式內通知新增至您的應用程式! -------------------------------------------------------------- ![再次](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/716b7biilet4auudjlcu.png) Novu 提供開源通知基礎架構和功能齊全的嵌入式通知中心。 這就是如何使用`React`建立 novu 元件以用於應用程式內通知。 ``` import { NovuProvider, PopoverNotificationCenter, NotificationBell, } from "@novu/notification-center"; function App() { return ( <> <NovuProvider subscriberId={process.env.REACT_APP_SUB_ID} applicationIdentifier={process.env.REACT_APP_APP_ID} > <PopoverNotificationCenter> {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />} </PopoverNotificationCenter> </NovuProvider> </> ); } export default App; ``` 您可以閱讀[文件](https://docs.novu.co/getting-started/introduction)。 https://github.com/novuhq/novu --- 6. [Remotion](https://github.com/remotion-dev/remotion) - 使用 React 以程式設計方式製作影片。 ------------------------------------------------------------------------------- ![遠端](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmnrxhsc7b9mm5oagflm.png) 使用 React 建立真正的 MP4 影片,使用伺服器端渲染和參數化擴展影片製作。 開始使用以下 npm 指令。 ``` npm init video ``` 它為您提供了一個幀號和一個空白畫布,您可以在其中使用 React 渲染任何您想要的內容。 這是一個範例 React 元件,它將當前幀渲染為文字。 ``` import { AbsoluteFill, useCurrentFrame } from "remotion";   export const MyComposition = () => { const frame = useCurrentFrame();   return ( <AbsoluteFill style={{ justifyContent: "center", alignItems: "center", fontSize: 100, backgroundColor: "white", }} > The current frame is {frame}. </AbsoluteFill> ); }; ``` 您可以閱讀[文件](https://www.remotion.dev/docs/)。 過去兩年,remotion 團隊因製作 GitHub Wrapped 而聞名。 https://github.com/remotion-dev/remotion --- [7.NocoDB](https://github.com/nocodb/nocodb) - Airtable 的替代品。 ------------------------------------------------------------- ![諾科資料庫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw3tchfgyzehye5c39xq.png) Airtable 的免費開源替代品是 NocoDB。它可以使用任何 MySQL、PostgreSQL、SQL Server、SQLite 或 MariaDB 資料庫製作智慧型電子表格。 其主要目標是讓強大的計算工具得到更廣泛的使用。 開始使用以下 npx 指令。 ``` npx create-nocodb-app ``` 您可以閱讀[文件](https://docs.nocodb.com/)。 NocoDB 的建立是為了為世界各地的數位企業提供強大的開源和無程式碼資料庫介面。 您可以非常快速地將airtable資料匯入NocoDB。 https://github.com/nocodb/nocodb --- 8.[新穎](https://github.com/steven-tey/novel)- 所見即所得編輯器,具有人工智慧自動完成功能。 ------------------------------------------------------------------- ![小說](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo34vd9twpxcpbpzgchi.png) 它使用`Next.js` 、 `Vercel AI SDK` 、 `Tiptap`作為文字編輯器。 開始使用以下 npm 指令。 ``` npm i novel ``` 您可以這樣使用它。有多種選項可用於改進您的應用程式。 ``` import { Editor } from "novel"; export default function App() { return <Editor />; } ``` https://github.com/steven-tey/novel --- 9. [Blitz](https://github.com/blitz-js/blitz) - 缺少 NextJS 的全端工具包。 ----------------------------------------------------------------- ![閃電戰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vz6ineg1o7xyv7pwbuqn.png) Blitz 繼承了 Next.js 的不足,為全球應用程式的交付和擴展提供了經過實戰考驗的函式庫和約定。 開始使用以下 npm 指令。 ``` npm install -g blitz ``` 這就是您如何使用 Blitz 建立新頁面。 ``` const NewProjectPage: BlitzPage = () => { const router = useRouter() const [createProjectMutation] = useMutation(createProject) return ( <div> <h1>Create New Project</h1> <ProjectForm submitText="Create Project" schema={CreateProject} onSubmit={async (values) => { // This is equivalent to calling the server function directly const project = await createProjectMutation(values) // Notice the 'Routes' object Blitz provides for routing router.push(Routes.ProjectsPage({ projectId: project.id })) }} /> </div> ); }; NewProjectPage.authenticate = true NewProjectPage.getLayout = (page) => <Layout>{page}</Layout> export default NewProjectPage ``` 您可以閱讀[文件](https://blitzjs.com/docs/get-started)。 它使建築物改善了數倍。 ![閃電戰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc4mb5wdksjv1ybx71co.png) https://github.com/blitz-js/blitz --- 10. [Supabase](https://github.com/supabase/supabase) - 開源 Firebase 替代品。 ----------------------------------------------------------------------- ![蘇帕貝斯](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksfygjhrzhmsg9cnvobs.png) 我們大多數人都已經預料到 SUPABASE 會出現在這裡,因為它實在是太棒了。 開始使用以下 npm 指令 (Next.js)。 ``` npx create-next-app -e with-supabase ``` 這是使用 supabase 建立用戶的方法。 ``` import { createClient } from '@supabase/supabase-js' // Initialize const supabaseUrl = 'https://chat-room.supabase.co' const supabaseKey = 'public-anon-key' const supabase = createClient(supabaseUrl, supabaseKey) // Create a new user const { user, error } = await supabase.auth.signUp({ email: '[email protected]', password: 'example-password', }) ``` 您可以閱讀[文件](https://supabase.com/docs)。 您可以使用身份驗證、即時、邊緣功能、儲存等功能建立一個速度極快的應用程式。 Supabase 涵蓋了這一切! 他們還提供了一些入門套件,例如 AI 聊天機器人和 Stripe 訂閱。 https://github.com/supabase/supabase --- [11.Refine](https://github.com/refinedev/refine) - 企業開源重組工具。 ------------------------------------------------------------ ![精煉](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qx0kd6t2jzdtf90k5ke3.png) 建立具有無與倫比的靈活性的管理面板、儀表板和 B2B 應用程式 您可以在一分鐘內使用單一 CLI 命令進行設定。 它具有適用於 15 多個後端服務的連接器,包括 Hasura、Appwrite 等。 開始使用以下 npm 指令。 ``` npm create refine-app@latest ``` 這就是使用 Refine 新增登入資訊的簡單方法。 ``` import { useLogin } from "@refinedev/core"; const { login } = useLogin(); ``` 您可以閱讀[文件](https://refine.dev/docs/)。 https://github.com/refinedev/refine --- 12. [Zenstack](https://github.com/zenstackhq/zenstack) - 資料庫到 API 和 UI 只需幾分鐘。 ----------------------------------------------------------------------------- ![禪斯塔克](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3b6n2ea3jeeva6uujoex.png) TypeScript 工具包,透過強大的存取控制層增強 Prisma ORM,並釋放其全端開發的全部功能。 開始使用以下 npx 指令。 ``` npx zenstack@latest init ``` 這是透過伺服器適配器建立 RESTful API 的方法。 ``` // pages/api/model/[...path].ts import { requestHandler } from '@zenstackhq/next'; import { enhance } from '@zenstackhq/runtime'; import { getSessionUser } from '@lib/auth'; import { prisma } from '@lib/db'; // Mount Prisma-style APIs: "/api/model/post/findMany", "/api/model/post/create", etc. // Can be configured to provide standard RESTful APIs (using JSON:API) instead. export default requestHandler({ getPrisma: (req, res) => enhance(prisma, { user: getSessionUser(req, res) }), }); ``` 您可以閱讀[文件](https://zenstack.dev/docs/welcome)。 https://github.com/zenstackhq/zenstack --- 13. [Buildship](https://github.com/rowyio/buildship) - 低程式碼視覺化後端建構器。 -------------------------------------------------------------------- ![建造船](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzlrynz5xephv4t9layd.png) 對於您正在使用無程式碼應用程式建構器(FlutterFlow、Webflow、Framer、Adalo、Bubble、BravoStudio...)或前端框架(Next.js、React、Vue...)建立的應用程式,您需要一個後端來支援可擴展的 API、安全工作流程、自動化等。BuildShip 為您提供了一種完全視覺化的方式,可以在易於使用的完全託管體驗中可擴展地建立這些後端任務。 這意味著您不需要在雲端平台上爭論或部署東西、執行 DevOps 等。只需立即建置和交付 🚀 https://github.com/rowyio/buildship --- 14. [Taipy](https://github.com/Avaiga/taipy) - 將資料和人工智慧演算法整合到生產就緒的 Web 應用程式中。 ----------------------------------------------------------------------------- ![打字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohv3johuz92lsaux52oq.png) Taipy 是一個開源 Python 庫,用於輕鬆的端到端應用程式開發, 具有假設分析、智慧管道執行、內建調度和部署工具。 開始使用以下命令。 ``` pip install taipy ``` 這是一個典型的Python函數,也是過濾器場景中使用的唯一任務。 ``` def filter_genre(initial_dataset: pd.DataFrame, selected_genre): filtered_dataset = initial_dataset[initial_dataset['genres'].str.contains(selected_genre)] filtered_data = filtered_dataset.nlargest(7, 'Popularity %') return filtered_data ``` 您可以閱讀[文件](https://docs.taipy.io/en/latest/)。 他們還有很多可供您建立的[演示應用程式教學](https://docs.taipy.io/en/latest/knowledge_base/demos/)。 https://github.com/Avaiga/taipy --- 15. [LocalForage](https://github.com/localForage/localForage) - 改進了離線儲存。 ------------------------------------------------------------------------ ![當地飼料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hlrka5pybvmgmo2djel.png) LocalForage 是一個 JavaScript 函式庫,它透過使用非同步資料儲存和簡單的、類似 localStorage 的 API 來改善 Web 應用程式的離線體驗。它允許開發人員儲存多種類型的資料而不僅僅是字串。 開始使用以下 npm 指令。 ``` npm install localforage ``` 只需包含 JS 檔案並開始使用 localForage。 ``` <script src="localforage.js"></script> ``` 您可以閱讀[文件](https://localforage.github.io/localForage/#installation)。 https://github.com/localForage/localForage --- 16. [Zod](https://github.com/colinhacks/zod) - 使用靜態類型推斷的 TypeScript-first 模式驗證。 ------------------------------------------------------------------------------- ![佐德](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s6zvmqr0lv93vsrhofs.png) Zod 的目標是透過最大限度地減少重複的類型聲明來對開發人員友好。使用 Zod,您聲明一次驗證器,Zod 將自動推斷靜態 TypeScript 類型。將更簡單的類型組合成複雜的資料結構很容易。 開始使用以下 npm 指令。 ``` npm install zod ``` 這是您在建立字串架構時自訂一些常見錯誤訊息的方法。 ``` const name = z.string({ required_error: "Name is required", invalid_type_error: "Name must be a string", }); ``` 您可以閱讀[文件](https://zod.dev/)。 它適用於 Node.js 和所有現代瀏覽器 https://github.com/colinhacks/zod --- 17.[多普勒](https://github.com/DopplerHQ)- 管理你的秘密。 ----------------------------------------------- ![多普勒](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gycxnuiiwsvibryrytlc.png) 您可以透過在具有開發、暫存和生產環境的專案中組織機密來消除機密蔓延。 開始使用以下指令 (MacOS)。 ``` $ brew install dopplerhq/cli/doppler $ doppler --version ``` 這是安裝 Doppler CLI[的 GitHub Actions 工作流程](https://github.com/DopplerHQ/cli-action)。 您可以閱讀[文件](https://docs.doppler.com/docs/start)。 ``` name: Example action on: [push] jobs: my-job: runs-on: ubuntu-latest steps: - name: Install CLI uses: dopplerhq/cli-action@v3 - name: Do something with the CLI run: doppler secrets --only-names env: DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }} ``` https://github.com/DopplerHQ --- 18. [FastAPI](https://github.com/tiangolo/fastapi) - 高效能、易於學習、快速編碼、可用於生產。 ------------------------------------------------------------------------- ![快速API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2awncoia6255ycl95lk.png) FastAPI 是一個現代、快速(高效能)的 Web 框架,用於基於標準 Python 類型提示使用 Python 3.8+ 建立 API。 開始使用以下命令。 ``` $ pip install fastapi ``` 這是您開始使用 FastAPI 的方式。 ``` from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` 您的編輯器將自動完成屬性並了解它們的類型,這是使用 FastAPI 的最佳功能之一。 您可以閱讀[文件](https://fastapi.tiangolo.com/)。 https://github.com/tiangolo/fastapi --- 19. [Flowise](https://github.com/FlowiseAI/Flowise) - 拖放 UI 來建立您的客製化 LLM 流程。 ---------------------------------------------------------------------------- ![流動](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct732wv07pvwx0nmavp5.png) Flowise 是一款開源 UI 視覺化工具,用於建立客製化的 LLM 編排流程和 AI 代理程式。 開始使用以下 npm 指令。 ``` npm install -g flowise npx flowise start OR npx flowise start --FLOWISE_USERNAME=user --FLOWISE_PASSWORD=1234 ``` 這就是整合 API 的方式。 ``` import requests url = "/api/v1/prediction/:id" def query(payload): response = requests.post( url, json = payload ) return response.json() output = query({ question: "hello!" )} ``` 您可以閱讀[文件](https://docs.flowiseai.com/)。 https://github.com/FlowiseAI/Flowise --- 20. [Scrapy](https://github.com/scrapy/scrapy) - Python 的快速進階網頁爬行和抓取框架.. ------------------------------------------------------------------------ ![鬥志旺盛](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1b2y1hzdsphw43b6v7b.png) Scrapy 是一個快速的高級網路爬行和網頁抓取框架,用於爬行網站並從頁面中提取結構化資料。它可用於多種用途,從資料探勘到監控和自動化測試。 開始使用以下命令。 ``` pip install scrapy ``` 建造並執行您的網路蜘蛛。 ``` pip install scrapy cat > myspider.py <<EOF import scrapy class BlogSpider(scrapy.Spider): name = 'blogspider' start_urls = ['https://www.zyte.com/blog/'] def parse(self, response): for title in response.css('.oxy-post-title'): yield {'title': title.css('::text').get()} for next_page in response.css('a.next'): yield response.follow(next_page, self.parse) EOF scrapy runspider myspider.py ``` 您可以閱讀[文件](https://scrapy.org/doc/)。 它擁有大約 50k+ 的星星,因此對於網頁抓取來說具有巨大的可信度。 https://github.com/scrapy/scrapy --- 21. [Tone](https://github.com/Tonejs/Tone.js) - 在瀏覽器中製作互動式音樂。 ------------------------------------------------------------- ![音調.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fokxsoblaohgs4tx75g3.png) 開始使用以下 npm 指令。 ``` npm install tone ``` 這是您開始使用 Tone.js 的方法 ``` // To import Tone.js: import * as Tone from 'tone' //create a synth and connect it to the main output (your speakers) const synth = new Tone.Synth().toDestination(); //play a middle 'C' for the duration of an 8th note synth.triggerAttackRelease("C4", "8n"); ``` 您可以閱讀[文件](https://github.com/Tonejs/Tone.js?tab=readme-ov-file#installation)。 https://github.com/Tonejs/Tone.js --- 22. [Spacetime](https://github.com/spencermountain/spacetime) - 輕量級 javascript 時區庫。 ----------------------------------------------------------------------------------- ![時空](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abfyfuzt4nw4h7b8usab.png) 您可以計算遠端時區的時間;支持夏令時、閏年和半球。按季度、季節、月份、週來定位時間.. 開始使用以下 npm 指令。 ``` npm install spacetime ``` 您可以這樣使用它。 ``` <script src="https://unpkg.com/spacetime"></script> <script> var d = spacetime('March 1 2012', 'America/New_York') //set the time d = d.time('4:20pm') d = d.goto('America/Los_Angeles') d.time() //'1:20pm' </script> ``` https://github.com/spencermountain/spacetime --- 23. [Mermaid](https://github.com/mermaid-js/mermaid) - 從類似 markdown 的文字產生圖表。 ---------------------------------------------------------------------------- ![美人魚](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggubn86xv7fznxol6fw7.png) 您可以使用 Markdown with Mermaid 等文字產生流程圖或序列圖等圖表。 這就是建立圖表的方法。 ``` sequenceDiagram Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! ``` 它將做出如下圖。 ![圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbuo2ey5q2x3sjwywizg.png) 您可以閱讀[VS Code](https://docs.mermaidchart.com/plugins/visual-studio-code)的[文件](https://mermaid.js.org/intro/getting-started.html)和外掛程式。 請參閱[即時編輯器](https://mermaid.live/edit#pako:eNpVkE1PwzAMhv9KlvM-2AZj62EIxJd24ADXXLzEbaKlcUkdUDX1v5MONomcnNevXz32UWoyKAvZ4mfCoPHRQRWhVuHeO42T7XZHNhTiFb0nMdRjYelbQETRUbpTwRM1uQ2erbaoDyqI_AbnZfjZVZYFVOBCy8J2DWlLwUQHKmAwKrwRo4gnF5Xid-gd2FEAL9hSyp12pMIpNcee2ArxEhH4LG-3D7TPoAPcnhL_4WVxcgHZkfedqIjMSI5ljbEGZ_LyxwFaSbZYo5JFLg3Eg5Iq9NkHiemjC1oWHBOOZWoM8PlQ_8Un45iiLErwbRY9gcH8PUrumuHKlWs5J2oKpasGPUWfZcvctMVsNrSnlWOb9lNN9ax1xkJk-7VZzVaL1RoWS1zdLuFmuTR6P9-sy8X1vDS3V_MFyL7vfwD_bJ1W)中的範例。 https://github.com/mermaid-js/mermaid --- 24.[公共 API](https://github.com/public-apis/public-apis) - 20 多個類別的 1400 多個 API。 ------------------------------------------------------------------------------- ![公共API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjapk9rwlzdl6bcyqdnl.png) 我們主要使用外部 API 來建立應用程式,在這裡您可以找到所有 API 的清單。網站連結在最後。 它在 GitHub 上擁有大約 279k+ 顆星。 ![公共API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rld5i88smezo1naawz7a.png) 從儲存庫取得網站連結非常困難。所以,我把它貼在這裡。 網址 - [Collective-api.vercel.app/](https://collective-api.vercel.app/) https://github.com/public-apis/public-apis --- 25. [Framer Motion](https://github.com/framer/motion) - 像魔法一樣的動畫。 ----------------------------------------------------------------- ![成幀器運動](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn4ecqkrhs8f4729bzps.png) 可用的最強大的動畫庫之一。 Framer 使用簡單的聲明性語法意味著您編寫的程式碼更少。更少的程式碼意味著您的程式碼庫更易於閱讀和維護。 您可以建立事件和手勢,並且使用 Framer 的社區很大,這意味著良好的支援。 開始使用以下 npm 指令。 ``` npm install framer-motion ``` 您可以這樣使用它。 ``` import { motion } from "framer-motion" <motion.div whileHover={{ scale: 1.2 }} whileTap={{ scale: 1.1 }} drag="x" dragConstraints={{ left: -100, right: 100 }} /> ``` 您可以閱讀[文件](https://www.framer.com/motion/introduction/)。 https://github.com/framer/motion --- 26.[順便說一句](https://github.com/btw-so/btw)- 在幾分鐘內建立您的個人部落格。 ---------------------------------------------------------- ![順便提一句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnne3lrfpolotmxkdz2m.png) 順便說一句,您可以註冊並使用,而無需安裝任何東西。您也可以使用開源版本自行託管。 ![順便提一句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rli7hpoccqwpvba29b4.png) 使用順便說一句建立的[範例部落格](https://www.siddg.com/about)。 https://github.com/btw-so/btw --- 27. [Formbricks](https://github.com/formbricks/formbricks) - 開源調查平台。 -------------------------------------------------------------------- ![成型磚](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tp6ggyom33vdifd3m1vt.png) Formbricks 提供免費、開源的測量平台。透過精美的應用程式內、網站、連結和電子郵件調查收集用戶旅程中每個點的回饋。在 Formbricks 之上建置或利用預先建置的資料分析功能。 開始使用以下 npm 指令。 ``` npm install @formbricks/js ``` 這就是您開始使用 formbricks 的方法。 ``` import formbricks from "@formbricks/js"; if (typeof window !== "undefined") { formbricks.init({ environmentId: "claV2as2kKAqF28fJ8", apiHost: "https://app.formbricks.com", }); } ``` 您可以閱讀[文件](https://formbricks.com/docs/getting-started/quickstart-in-app-survey)。 https://github.com/formbricks/formbricks --- 28. [Stripe](https://github.com/stripe) - 支付基礎設施。 ------------------------------------------------- ![條紋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79yvcgsi4744cmryh15j.png) 數以百萬計的各種規模的公司在線上和親自使用 Stripe 來接受付款、發送付款、自動化財務流程並最終增加收入。 開始使用以下 npm 指令 (React.js)。 ``` npm install @stripe/react-stripe-js @stripe/stripe-js ``` 這就是使用鉤子的方法。 ``` import React, {useState} from 'react'; import ReactDOM from 'react-dom'; import {loadStripe} from '@stripe/stripe-js'; import { PaymentElement, Elements, useStripe, useElements, } from '@stripe/react-stripe-js'; const CheckoutForm = () => { const stripe = useStripe(); const elements = useElements(); const [errorMessage, setErrorMessage] = useState(null); const handleSubmit = async (event) => { event.preventDefault(); if (elements == null) { return; } // Trigger form validation and wallet collection const {error: submitError} = await elements.submit(); if (submitError) { // Show error to your customer setErrorMessage(submitError.message); return; } // Create the PaymentIntent and obtain clientSecret from your server endpoint const res = await fetch('/create-intent', { method: 'POST', }); const {client_secret: clientSecret} = await res.json(); const {error} = await stripe.confirmPayment({ //`Elements` instance that was used to create the Payment Element elements, clientSecret, confirmParams: { return_url: 'https://example.com/order/123/complete', }, }); if (error) { // This point will only be reached if there is an immediate error when // confirming the payment. Show error to your customer (for example, payment // details incomplete) setErrorMessage(error.message); } else { // Your customer will be redirected to your `return_url`. For some payment // methods like iDEAL, your customer will be redirected to an intermediate // site first to authorize the payment, then redirected to the `return_url`. } }; return ( <form onSubmit={handleSubmit}> <PaymentElement /> <button type="submit" disabled={!stripe || !elements}> Pay </button> {/* Show error message to your customers */} {errorMessage && <div>{errorMessage}</div>} </form> ); }; const stripePromise = loadStripe('pk_test_6pRNASCoBOKtIshFeQd4XMUh'); const options = { mode: 'payment', amount: 1099, currency: 'usd', // Fully customizable with appearance API. appearance: { /*...*/ }, }; const App = () => ( <Elements stripe={stripePromise} options={options}> <CheckoutForm /> </Elements> ); ReactDOM.render(<App />, document.body); ``` 您可以閱讀[文件](https://github.com/stripe/react-stripe-js?tab=readme-ov-file#minimal-example)。 您幾乎可以整合任何東西。它有一個巨大的選項清單。 ![整合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67f3pb2i8xolt635rp2p.png) https://github.com/stripe --- 29. [Upscayl](https://github.com/upscayl/upscayl) - 開源 AI 影像升級器。 ---------------------------------------------------------------- ![高級](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2c1837rev5jb260ro2sd.png) 適用於 Linux、MacOS 和 Windows 的免費開源 AI Image Upscaler 採用 Linux 優先概念建構。 它可能與全端無關,但它對於升級圖像很有用。 ![高級](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4qq1wm3wey3vihn9al4.png) 透過最先進的人工智慧,Upscayl 可以幫助您將低解析度影像變成高解析度。清脆又鋒利! https://github.com/upscayl/upscayl --- 30.[重新發送](https://github.com/resend)- 為開發人員提供的電子郵件 API。 ------------------------------------------------------- ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3auhh3hbxjmmzehe5v0.png) 您可以使用 React 建立和傳送電子郵件。 2023 年最受炒作的產品之一。 開始使用以下 npm 指令。 ``` npm install @react-email/components -E ``` 這是將其與 next.js 專案整合的方法。 ``` import { EmailTemplate } from '@/components/email-template'; import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); export async function POST() { const { data, error } = await resend.emails.send({ from: '[email protected]', to: '[email protected]', subject: 'Hello world', react: EmailTemplate({ firstName: 'John' }), }); if (error) { return Response.json({ error }); } return Response.json(data); } ``` 您可以閱讀[文件](https://resend.com/docs/introduction)。 ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rer9ym187e4i9l11afkg.png) 基本概念是一個簡單、優雅的介面,讓您可以在幾分鐘內開始發送電子郵件。它可以透過適用於您最喜歡的程式語言的 SDK 直接融入您的程式碼中。 https://github.com/resend --- 哇!如此長的專案清單。 我知道您有更多想法,分享它們,讓我們一起建造:D 如今建立全端應用程式並不難,但每個應用程式都可以透過有效地使用優秀的開源專案來解決任何問題來增加這一獨特因素。 例如,您可以建立一些提供通知或建立 UI 流來抓取資料的東西。 我希望其中一些內容對您的開發之旅有用。他們擁有一流的開發人員經驗;你可以依賴他們。 由於您將要建造東西,因此您可以在這裡找到一些[瘋狂的想法](https://github.com/florinpop17/app-ideas)。 祝你有美好的一天!直到下一次。 --- 原文出處:https://dev.to/copilotkit/im-building-a-full-stack-app-here-are-the-libraries-im-going-to-use-51nk

warp - 使用命令列的新方法、新工具

![warp 終端機窗口](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iabku5u670oeynz1f4ug.png) **這是這十年來作業系統命令列介面發生的最具創新性的事情!💡** 您現在可能想知道“這是什麼?🤔”。 簡單來說,warp 是類別 Unix 作業系統中終端的替代品。這意味著您可以使用 Warp 在終端上完成工作,速度更快,使用者更友善。💫 閒聊夠了,讓我們深入了解 Warp 比內建終端(或現在已經安裝的終端)更好的原因。 現在您可能已經在使用自訂終端/外掛程式或利用內建終端功能的主題,例如: > iTerm2 > Oh-my-zsh(包括 powerlevel10k 等主題) 這些終端提供了一些很酷的功能,例如自動完成、自動更正、建議、顏色編碼等。 但 Warp 已經超越了這些實現,表明 Terminal 可以做得更好。 **扭曲的特點** > 1. 很像 IDE ![Warp IDE,如 Termnial.gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfof63rka91kv0zwmwoo.gif) 除了普通的命令列 UX(使用者體驗)之外,Warp 還允許您像在 IDE 中一樣插入、選擇或複製。您可以透過鍵盤或滑鼠控制遊標。它還無需任何插件即可執行智慧自動完成。 > 2. 人工智慧 ![扭曲終端 AI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4km49tyeczeego1j9o6w.png) Warp 具有內建的 AI 功能。您可以與 Warp AI 聊天並解決所有編碼問題,甚至無需離開終端。 Warp AI 還包括 AI 命令建議,可以透過在命令列上鍵入 CTRL-` 或 # 來存取。 ![為指令啟用扭曲 AI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/828gzsymsn3444ve76fb.jpeg) 最酷的事情是您的請求保持私密和安全,並且永遠不會用於訓練公共模型。 > 3. 協作終端 ![Warp 協作環境](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t1f4zsdq7u0auhlbbk6p.png) Warp 讓您可以使用 Warp 帳戶進行團隊共享和工作(此帳戶需要在開始時建立,目前是強制性的)。這是迄今為止我在任何其他終端中都沒有見過的功能。 > 4. 終端工作流程 ![變形工作流程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjewsa2m11llqu9321nf.jpeg) 這使您可以保存和組織您建立的難以記住的自訂命令,以便您以後可以輕鬆地重複使用它們。這可以保存您的 Warp 驅動器(Warp 開發團隊提供的線上儲存解決方案),然後可以與您所有的朋友共用。 > 5. 製作您自己的自訂主題。 ![變形主題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e1ib6c5tyvcbj9zlie16.png) 終端主題已經存在很久了,但沒有人能像 Warp 一樣做到這一點。 Warp 為您提供了一些內建主題可供選擇。但最好的部分是它允許您透過從自訂背景圖像自動生成調色板來建立自己的調色板,或者您可以編寫自己的主題。 **你想嘗試嗎?** 您可以使用 Warp 並親自體驗這些功能,您可以使用以下方法將其安裝到您的系統中, 1. [從此連結前往 Warp 官方頁面](https://www.warp.dev/) 2. 您將看到一個根據您的系統下載軟體包安裝程式的按鈕。目前僅適用於 macOS 和 Linux。很快就會新增 Windows 支援。 3. 您可以使用安裝程式來安裝它,就像安裝其他軟體一樣。 或者 如果您的系統中安裝了`brew` ,則可以執行以下命令輕鬆下載並安裝Warp。 `brew install --cask warp` 初始設定將要求您建立並登入 Warp 帳戶,該帳戶將在您首次開啟 Warp 時進行引導。您可以使用 GitHub 或 Google 帳戶輕鬆建立 Warp 帳戶。 結論 我親自嘗試了 Warp,並對這種建立更友善的命令列介面的新方法感到非常積極。隨著程式技術的進步,開發人員為現代框架、插件和套件甚至 IDE 等語言提出了許多創新的東西,但沒有太多創新集中在命令列介面上,這使得新開發人員思考命令line 很無聊,盡量避免它,導致缺乏終端命令的知識和經驗。 諸如此類的措施讓命令列介面更加受到關注,並使這些新開發人員對使用命令列更感興趣。 Warp 開發團隊做得很好,透過寫這篇文章,我想為這個專案提供更多的影響力和認識,因為我是這個專案的支持方。 > 所有圖像和影片均從 Warp 官方網站獲得,並註明來源。 🙌 --- 原文出處:https://dev.to/chamal1120/warp-a-new-way-to-use-your-command-line-1hp6

有興趣為開源做出貢獻嗎?這是您需要了解的

您是否想過為開源做出貢獻?如果你在這裡,你可能就這麼做了😄 對於初學者來說,這可能會讓人感到困惑,但我可以理解——我自己也經歷過這樣的情況。然而,您找到了繼續前進的意志力並了解了有關此過程的更多訊息,我希望本文將向您展示它並不像看起來那麼複雜。 大多數接受貢獻的儲存庫通常都有一個您應該留意的**CONTRIBUTING.MD**檔案。由於並非所有存儲庫都是相同的,因此該文件將告訴您有關為該特定存儲庫做出貢獻的過程的更多資訊。 然而,一些通用規則可以應用於大多數儲存庫,我們將在本文中討論它們。 ### 支持我們🙏🏻 ![GitHub 星星](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vt38z2e3fdro7h4u82ig.gif) 在我們開始之前,如果您為我們的[開源儲存庫](https://github.com/cyclops-ui/cyclops)加註星標並幫助我們獲得我們的產品,我們將非常高興 在其他開發者面前的工具 ⭐ 在哪裡貢獻? ------ 我想到的第一個問題是:在哪裡做出貢獻? 好吧,您應該從**您已經使用的**專案開始。也許某些庫需要更新,或者某些工具有錯誤? 您可能想為您的**專業領域**內的某些專案或使用您熟悉的**技術堆疊的**專案做出貢獻。 這些都是偉大的競爭者,你應該研究一下他們。 如果您不了解任何專案但仍想做出貢獻,請瀏覽 GitHub 或造訪[Quine](https://quine.sh/contribute)等網站,其中許多開源儲存庫正在尋找貢獻者。 在本文中,我們將使用我們的開源儲存庫 - [**Cyclops**](https://github.com/cyclops-ui/cyclops) 。 如何知道哪些方面需要改進? ------------- 無論您是在尋找事情做,還是已經知道需要修復的錯誤,所有貢獻都從同一個地方開始 -**問題選項卡**。 ![問題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fs9j1leiduys83giz360.png) 如果您是該專案的新手,您可以尋找大多數儲存庫都有的「 ***goodfirst issues」***標籤。顧名思義,它們是參與專案的一個很好的切入點。所有問題都應該有問題的描述。 如果您知道此處未列出的問題或錯誤,或者您希望看到新功能的引入,請開啟一個新問題!一旦您提出問題,維護人員將決定下一步要做什麼,您應該**在開始編碼之前等待他們的答案**。 **專業提示**:如果您要提出錯誤問題,請務必寫下如何重現該錯誤的步驟! 如何貢獻? ----- 好的,我們找到了一個儲存庫,這是我們想要解決的問題,與維護人員進行了交談,並獲得了解決該問題的綠燈。最後讓我們開始編碼吧! ### 1. 分叉倉庫 第一步是**分叉**儲存庫。這將**複製該專案**並將其新增至您的 GitHub 帳戶。 ![叉](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y2v05gd5fxwe25wmckzu.png) ### 2. 克隆倉庫 現在轉到您的儲存庫並找到分叉的儲存庫。點選**< > 程式碼**按鈕並選擇選項之一 (HTTPS / SSH / GitHub CLI)。 ![複製](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ddnmxdf9vz4kwoelcot.png) **複製框中的內容**。現在打開終端並將自己定位到要在本地儲存專案的位置。一旦您定位好自己,請在終端機中輸入以下命令: ``` git clone <paste the copied content> ``` 片刻之後,您的 PC 上就應該有該專案了! ### 3. 建立新分支 現在,轉到本機資料夾並建立新分支。請務必查看專案的**CONTRIBUTE.md** ,看看維護者是否希望您遵循一些**分支命名**規則! ### 4. 提交並推動您的更改 一旦準備好分支,您就可以開始更改程式碼庫。完成後,提交更改並將其推送到您的分叉存儲庫。如果儲存庫已就位,請務必遵循**提交訊息約定**(檢查**CONTRIBUTING.md** )。 ### 5. 發起拉取請求 現在您已經推送了更改並希望將它們合併到主存儲庫,是時候建立拉取請求了!再次,您應該檢查**CONTRIBUTING.md**規則,看看維護者是否希望您**在建立 PR 時遵循命名約定**以及他們希望在描述中看到什麼。 ![開設公關](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zou62itckl8p1fx1s80j.png) ❗請務必將`base repository`設定為您衍生的原始儲存庫❗ 我建立了一個 PR,現在怎麼辦? ---------------- 您對所做的更改感到滿意,並成功建立了對主儲存庫的拉取請求。現在怎麼辦?現在,你**等著吧**。 根據您的 PR 正在修復的問題的緊急程度以及維護人員的日程安排,您將不得不等待有人**審核**您的拉取請求。準備好解釋為什麼以及你做了什麼(如果你在 PR 描述中沒有做好),並在必要時**進行更改**。 不要以個人名義接受任何變更要求。你們所有人都是為了專案的進步而來到這裡,沒有人懷有惡意。**如果您不同意**審稿人的意見,**請告訴他們**!健康的討論從來都不是壞事。 為什麼分叉? 🍴 -------- 您可能想知道為什麼我們必須分叉存儲庫。為什麼不直接克隆原始版本並在單獨的分支上工作?基本上,刪除#1 步驟,其餘的不是一樣嗎? 好吧,您可以嘗試,但是一旦您推送更改,您就會發現您**沒有這樣做的授權**!透過分叉儲存庫,您將成為複製儲存庫的擁有者,並**有權更改程式碼庫**。這是一個簡潔的系統,可以確保只有原始維護者批准的變更才能通過。 去貢獻吧! ----- 現在您已經掌握了這些訊息,您就可以開始在開源世界中留下自己的印記了!去幫助您已經聽說過和沒有聽說過的無數專案,並加入這個不斷發展的社區。我們將非常感謝您的幫助,我確信這一點😉 您知道有哪些開源專案帶有很多「第一期好」的標籤嗎?請在評論中分享! --- 原文出處:https://dev.to/cyclops-ui/interested-in-contributing-to-open-source-heres-what-you-need-to-know-g1c

我如何建立 NotesGPT – 一個全端人工智慧語音筆記應用程式

上週,我推出了[notesGPT](https://usenotesgpt.com/) ,這是一款免費開源語音記事應用程式,上週迄今為止已有[35,000 名訪客](https://twitter.com/nutlope/status/1760053364791050285)、7,000 名用戶和超過 1,000 名 GitHub star。它允許您錄製語音筆記,使用[Whisper](https://github.com/openai/whisper)進行轉錄,並透過[Together](https://together.ai/)使用 Mixtral 來提取操作項並將其顯示在操作項視圖中。它也是[完全開源的](https://github.com/nutlope/notesgpt),配備了身份驗證、儲存、向量搜尋、操作項,並且在行動裝置上完全響應,易於使用。 我將向您詳細介紹我是如何建造它的。 架構和技術堆疊 ------- 這是架構的快速圖表。我們將更深入地討論每個部分,並同時展示程式碼範例。 ![架構圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjl3i4bu23fn0pabldsw.png) 這是我使用的整體技術堆疊: - 資料庫和雲端函數的[convex](https://convex.dev/) - Next.js [App Router](https://nextjs.org/docs/app)框架 - [複製](https://replicate.com/)Whisper 轉錄 - LLM 與[JSON 模式](https://docs.together.ai/docs/json-mode)的[Mixtral](https://mistral.ai/news/mixtral-of-experts/) - [Together.ai](http://Together.ai)用於推理和嵌入 - 用於儲存語音註釋的[凸檔存儲](https://docs.convex.dev/file-storage) - [凸向量搜尋](https://docs.convex.dev/vector-search)用於向量搜尋 - 負責使用者身份驗證的[職員](https://clerk.dev/) - [Tailwind CSS](https://tailwindcss.com/)樣式 登陸頁面 ---- 該應用程式的第一部分是您導航到notesGPT 時看到的登入頁面。 ![NotesGPT 的登陸頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hfscmudh4l33oab3azw.png) 用戶首先看到的是這個登陸頁面,它與應用程式的其餘部分一起使用 Next.js 和 Tailwind CSS 進行樣式建立。我喜歡使用 Next.js,因為它可以輕鬆啟動 Web 應用程式並編寫 React 程式碼。 Tailwind CSS 也很棒,因為它允許您在網頁上快速迭代,同時與 JSX 保持在同一檔案中。 與 Clerk 和 Convex 進行身份驗證 ----------------------- 當使用者點擊主頁上的任一按鈕時,他們將被導向到登入畫面。這是由 Clerk 提供支援的,這是一個與 Convex 很好整合的簡單身份驗證解決方案,我們將在整個後端使用它,包括雲端功能、資料庫、儲存和向量搜尋。 ![認證頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02khgd6f2jfew1w7dufn.png) Clerk 和 Convex 都很容易設定。您只需在這兩個服務上建立一個帳戶,安裝它們的 npm 庫,執行`npx convex dev`來設定您的凸資料夾,然後建立一個如下所示的`ConvexProvider.ts`檔案來包裝您的應用程式。 ``` 'use client'; import { ReactNode } from 'react'; import { ConvexReactClient } from 'convex/react'; import { ConvexProviderWithClerk } from 'convex/react-clerk'; import { ClerkProvider, useAuth } from '@clerk/nextjs'; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export default function ConvexClientProvider({ children, }: { children: ReactNode; }) { return ( <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!} > <ConvexProviderWithClerk client={convex} useAuth={useAuth}> {children} </ConvexProviderWithClerk> </ClerkProvider> ); } ``` 請查看[Convex Quickstart](https://docs.convex.dev/quickstart/nextjs)和[Convex Clerk](https://docs.convex.dev/auth/clerk) auth 部分以了解更多詳細資訊。 設定我們的架構 ------- 您可以在有或沒有模式的情況下使用 Convex。就我而言,我知道資料的結構並想要定義它,所以我在下面這樣做了。這也為您提供了一個非常好的類型安全 API,可以在與資料庫互動時使用。我們定義兩個表格-一個用於儲存所有語音註解資訊的`notes`表和用於提取的操作專案的`actionItems`表。我們還將定義索引,以便能夠透過`userId`和`noteId`快速查詢資料。 ``` import { defineSchema, defineTable } from 'convex/server'; import { v } from 'convex/values'; export default defineSchema({ notes: defineTable({ userId: v.string(), audioFileId: v.string(), audioFileUrl: v.string(), title: v.optional(v.string()), transcription: v.optional(v.string()), summary: v.optional(v.string()), embedding: v.optional(v.array(v.float64())), generatingTranscript: v.boolean(), generatingTitle: v.boolean(), generatingActionItems: v.boolean(), }) .index('by_userId', ['userId']) .vectorIndex('by_embedding', { vectorField: 'embedding', dimensions: 768, filterFields: ['userId'], }), actionItems: defineTable({ noteId: v.id('notes'), userId: v.string(), task: v.string(), }) .index('by_noteId', ['noteId']) .index('by_userId', ['userId']), }); ``` 儀表板 --- 現在我們已經有了後端和身份驗證設定以及模式,我們可以看看如何獲取資料。登入應用程式後,用戶可以查看其儀表板,其中列出了他們錄製的所有語音筆記。 ![儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6u9f1b60kgfp4txbszur.png) 為此,我們首先在凸資料夾中定義一個查詢,該查詢使用 auth 接收`userId` ,驗證其有效,並傳回與使用者的`userId`相符的所有註解。 ``` export const getNotes = queryWithUser({ args: {}, handler: async (ctx, args) => { const userId = ctx.userId; if (userId === undefined) { return null; } const notes = await ctx.db .query('notes') .withIndex('by_userId', (q) => q.eq('userId', userId)) .collect(); const results = Promise.all( notes.map(async (note) => { const count = ( await ctx.db .query('actionItems') .withIndex('by_noteId', (q) => q.eq('noteId', note._id)) .collect() ).length; return { count, ...note, }; }), ); return results; }, }); ``` 之後,我們可以透過凸提供的函數使用使用者的驗證令牌來呼叫此`getNotes`查詢,以在儀表板中顯示所有使用者的註解。我們使用伺服器端渲染在伺服器上取得此資料,然後將其傳遞到`<DashboardHomePage />`客戶端元件。這也確保了客戶端上的資料也保持最新。 ``` import { api } from '@/convex/_generated/api'; import { preloadQuery } from 'convex/nextjs'; import DashboardHomePage from './dashboard'; import { getAuthToken } from '../auth'; const ServerDashboardHomePage = async () => { const token = await getAuthToken(); const preloadedNotes = await preloadQuery(api.notes.getNotes, {}, { token }); return <DashboardHomePage preloadedNotes={preloadedNotes} />; }; export default ServerDashboardHomePage; ``` 錄製語音筆記 ------ 最初,使用者的儀表板上不會有任何語音註釋,因此他們可以點擊「錄製新語音註釋」按鈕來錄製。他們將看到以下螢幕,允許他們進行錄製。 ![錄製語音筆記頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3lm22akd3zanf3ar0za.png) 這將使用本機瀏覽器 API 錄製語音筆記,將檔案保存在 Convex 檔案儲存中,然後透過 Replicate 將其傳送至 Whisper 進行轉錄。我們要做的第一件事是在凸資料夾中定義一個`createNote`突變,它將接收此記錄,在凸資料庫中保存一些訊息,然後呼叫耳語操作。 ``` export const createNote = mutationWithUser({ args: { storageId: v.id('_storage'), }, handler: async (ctx, { storageId }) => { const userId = ctx.userId; let fileUrl = (await ctx.storage.getUrl(storageId)) as string; const noteId = await ctx.db.insert('notes', { userId, audioFileId: storageId, audioFileUrl: fileUrl, generatingTranscript: true, generatingTitle: true, generatingActionItems: true, }); await ctx.scheduler.runAfter(0, internal.whisper.chat, { fileUrl, id: noteId, }); return noteId; }, }); ``` 耳語動作如下圖所示。它使用 Replicate 作為 Whisper 的託管提供者。 ``` export const chat = internalAction({ args: { fileUrl: v.string(), id: v.id('notes'), }, handler: async (ctx, args) => { const replicateOutput = (await replicate.run( 'openai/whisper:4d50797290df275329f202e48c76360b3f22b08d28c196cbc54600319435f8d2', { input: { audio: args.fileUrl, model: 'large-v3', translate: false, temperature: 0, transcription: 'plain text', suppress_tokens: '-1', logprob_threshold: -1, no_speech_threshold: 0.6, condition_on_previous_text: true, compression_ratio_threshold: 2.4, temperature_increment_on_fallback: 0.2, }, }, )) as whisperOutput; const transcript = replicateOutput.transcription || 'error'; await ctx.runMutation(internal.whisper.saveTranscript, { id: args.id, transcript, }); }, }); ``` 此外,所有這些檔案都可以在 Convex 儀表板的「檔案」下看到。 ![凸形儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz51ysreunwsk52tqjr9.png) 生成行動專案 ------ 使用者完成語音記錄並透過耳語進行轉錄後,輸出將傳遞到 Together AI 中。我們同時顯示此加載畫面。 ![頁面載入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rcr80meap2xql9nrzlf.png) 我們首先定義一個我們希望輸出所在的模式。然後,我們將此模式傳遞到 Together.ai 上託管的 Mixtral 模型中,並提示辨識語音註釋的摘要、文字記錄,並根據成績單。然後我們將所有這些資訊保存到 Convex 資料庫中。為此,我們在凸資料夾中建立一個凸動作。 ``` // convex/together.ts const NoteSchema = z.object({ title: z .string() .describe('Short descriptive title of what the voice message is about'), summary: z .string() .describe( 'A short summary in the first person point of view of the person recording the voice message', ) .max(500), actionItems: z .array(z.string()) .describe( 'A list of action items from the voice note, short and to the point. Make sure all action item lists are fully resolved if they are nested', ), }); export const chat = internalAction({ args: { id: v.id('notes'), transcript: v.string(), }, handler: async (ctx, args) => { const { transcript } = args; const extract = await client.chat.completions.create({ messages: [ { role: 'system', content: 'The following is a transcript of a voice message. Extract a title, summary, and action items from it and answer in JSON in this format: {title: string, summary: string, actionItems: [string, string, ...]}', }, { role: 'user', content: transcript }, ], model: 'mistralai/Mixtral-8x7B-Instruct-v0.1', response_model: { schema: NoteSchema, name: 'SummarizeNotes' }, max_tokens: 1000, temperature: 0.6, max_retries: 3, }); const { title, summary, actionItems } = extract; await ctx.runMutation(internal.together.saveSummary, { id: args.id, summary, actionItems, title, }); }); ``` 當 Together.ai 做出回應時,我們會看到最終畫面,使用者可以在左側的記錄和摘要之間切換,並查看並勾選右側的操作專案。 ![完整語音筆記頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnd6j68hgusa0aj2buhv.png) 向量搜尋 ---- 該應用程式的最後一部分是向量搜尋。我們使用 Together.ai 嵌入來嵌入文字記錄,並使人們可以根據文字記錄的語義在儀表板中進行搜尋。 我們透過在凸資料夾中建立一個`similarNotes`操作來實現此目的,該操作接受使用者的搜尋查詢,為其產生嵌入,並找到要在頁面上顯示的最相似的註釋。 ``` export const similarNotes = actionWithUser({ args: { searchQuery: v.string(), }, handler: async (ctx, args): Promise<SearchResult[]> => { // 1. Create the embedding const getEmbedding = await togetherai.embeddings.create({ input: [args.searchQuery.replace('/n', ' ')], model: 'togethercomputer/m2-bert-80M-32k-retrieval', }); const embedding = getEmbedding.data[0].embedding; // 2. Then search for similar notes const results = await ctx.vectorSearch('notes', 'by_embedding', { vector: embedding, limit: 16, filter: (q) => q.eq('userId', ctx.userId), // Only search my notes. }); return results.map((r) => ({ id: r._id, score: r._score, })); }, }); ``` 結論 -- 就像這樣,我們建立了一個可投入生產的全端人工智慧應用程式,配備身份驗證、資料庫、儲存和 API。請隨意查看[notesGPT,](https://usenotesgpt.com/)以從您的筆記或[GitHub 儲存庫](https://github.com/nutlope/notesGPT)產生操作專案以供參考。如果您有任何疑問,[請私訊我](twitter.com/nutlope),我將非常樂意回答! --- 原文出處:https://dev.to/nutlope/how-i-built-notesgpt-a-full-stack-ai-voice-note-app-265o

酷炫的個人網站,以及製作方法說明

對於未來的軟體工程師、設計師或產品經理來說,個人網站幾乎和履歷一樣成為標準——這是有充分理由的。個人網站是展示技術或設計悟性的好方法,並提供比標準簡歷更個性化和有趣的格式(而且您無論如何都可以將簡歷放在您的網站上)。網站比一張紙更具互動性,會讓您脫穎而出,並開啟潛在的對話主題。建立個人網站的方法有很多種,您應該仔細考慮您的方法——這將是您在網路上向招聘人員和許多臨時谷歌或linkedin搜尋者展示的方式。 接下來,我們將看看特別令人難忘的個人網站(前面很漂亮),並提供一些建立或更新您自己的網站的建議。 ### 個人網站的不同用途 個人網站可以實現許多不同的目的。我已經介紹了下面一些較大的類別。 #### 資料夾 對於藝術家或設計師來說,個人網站可以作為您的作品集。這是一種很棒的格式,並且很容易保持最新狀態。例如,考慮一下自由插畫家 Paddy Donnelly 的這個[網站](http://lefft.com/)。打開這些網站以獲得完整的體驗。 ![](https://thepracticaldev.s3.amazonaws.com/i/q2gq0m32cnjf6bv6soqf.png) #### 履歷 從最基本的形式來看,個人網站是讓您的履歷變得更有趣的好方法。即使從紙質簡歷中取出文字並在帶有電子郵件連結的網站上很好地格式化它也是一個很好的開始。例如,Jackie Luo 在她的[網站](http://jackieluo.com/)上提供了她簡歷的可讀版本。 ![](https://thepracticaldev.s3.amazonaws.com/i/4ylsr3ybq8azb3p49mlo.png) #### 以我為中心 即使您不想展示您的專業經驗,個人網站也是集中搜尋有關您自己的資訊的好方法。許多人在其網站上提供社交媒體帳戶的連結。例如,Safia Abdella 的[網站](https://safia.rocks/)乾淨、簡單,可以輕鬆存取造訪她網站的任何人可能需要的關鍵資訊。 ![](https://thepracticaldev.s3.amazonaws.com/i/mfg6uhfd1zwteo422d71.jpg) #### 部落格 個人網站是保存部落格的好地方,這是向訪客展示您的作品的好方法。阿萊娜·卡夫克斯 (Alaina Kafkes) 除了在 dev.to 和 Medium 上提供她的個人資料連結之外,還提供她[網站](http://alainakafk.es/#/words)上所有最新內容的連結。 ![](https://thepracticaldev.s3.amazonaws.com/i/9fsaresu0saxr195mwgm.png) #### 其他的東西 向網路講述您的故事。履歷、社群媒體資料,甚至你的 Facebook 頁面都受到相當嚴格的控制。網站是一個可以是任何你喜歡的空間:一個遊戲化的仙境,最少的描述,或其他什麼。考慮一下 Robby Leonardi 屢獲殊榮的遊戲化簡歷[網站](http://www.rleonardi.com/interactive-resume/)。 ![](https://thepracticaldev.s3.amazonaws.com/i/j69t5vlmeani2k8a67w3.png) ### 整個職業生涯中的個人網站 如果您是應屆畢業生或正在進行職業轉型,個人網站對技術招募人員來說會很有吸引力。早在 2013 年,《富比士》就報導稱,56% 的招募經理表示,與其他品牌工具相比,他們對候選人的個人網站印象更深刻。 作為未來的設計師或軟體工程師,您可以在頁面上展示您的技術能力!即使你不做技術性的事情,網站也比紙質簡歷更引人注目、更個性化,所以這是一個很好的方法,可以通過簡單的“在i-am-the-bomb.com 查看我的簡歷」來獲得優勢。 」。 當您繼續您的職業生涯時,您仍然可以保留個人網站來展示您正在從事的工作並維護您的個人品牌。例如,Cassidy Williams 在她的[網站](http://cassidoo.co/)上提供了有關她所做的事情的更新時間表。 ![](https://thepracticaldev.s3.amazonaws.com/i/9xnfkmkgtfi48c0id89b.png) 如果您正在尋找寫作和演講的機會,這是一個很好的地方,可以展示您的所作所為,並向在線查找您的任何人提供可存取的資訊。 隨著時間的推移維護您的網站可以讓您在開始另一次求職時輕鬆地短暫刷新,這也是吸引不可預見的機會和聯繫的好方法。我曾經有一個我不認識的表弟透過個人網站聯絡我——你永遠不知道! ### 入門 現在製作網站比以往任何時候都容易。那裡有一些很棒的入門教程。如果您想快速入門,我推薦[WordPress](http://www.wpbeginner.com/guides/)或[SquareSpace](https://developers.squarespace.com/beginner-tutorial/)的這些教學。如果您想建立和託管自己的, [Github Pages](https://guides.github.com/features/pages/)中的本指南是一個很好的起點。如果您想深入了解建置、託管和服務,這是一個很好的學習方式!以下是一些可能有用的資源: - MEAN 入門網站[儲存庫](https://github.com/manishrw/mean-starter-website) - Jekyll 入門套件儲存[庫](https://github.com/nirgn975/generator-jekyll-starter-kit) - Github 自己的 Web Starter it[儲存庫](https://github.com/google/web-starter-kit) - 關於工具和框架的[實用開發線程](https://dev.to/nayeonkim/what-toolframeworkcmsetc-do-you-use-to-build-your-own-personal-website) - 與實用開發文章相對應的[Twitter 線程](https://twitter.com/thepracticaldev/status/894161129492156416) ### 一般建議 1. **從某個地方開始。**人們很容易對一個網站感到興奮,努力獲取域名,將其加入到您的個人簡介中,在頁面上貼上“正在進行中”的標籤,然後完全忘記它。當我點擊某人的個人網站時,大約有 10-20% 的時間,該網站要么完全關閉,要么「正在進行」數月或數年。不要被所有令人驚嘆的網站嚇倒。作為一個初學者,至少要在大文本中加入指向您的相關帳戶和您的姓名的連結 - 這比看起來像一個無法完成他們開始的事情要好得多。 2. 從所有可能看到的人的角度**來批判性地審視您在網站上放置的內容**。雖然 Twitter 和 linkedin 帳戶很棒,但如果您不希望招聘人員看到您的 tumblr 頁面上有關野貓的訊息,請不要將其連結到那裡。同樣,如果你認為你的黑客馬拉松專案在更好的Tinder 上對公司來說看起來很棒,但可能會讓你的父母不高興,那麼你可以將你的個人網站從你的Facebook 公開資料中刪除。有時我們都可以提醒網路是公共的! 3. **並非您的所有作品都需要展示。**個人網站可以是展示您早期專案的有趣方式,儘管您在七年級製作的海報可能會讓您感到溫暖和懷舊,但它可能會引起招聘人員的懷疑。選擇最能展現你的作品。 4. **讓它個性化。**這是您的個人網站是有原因的。不要害怕在你的網站上放一些東西。例如,Terri Burns 在她的[網站](http://tcburning.com/)上分享了她的興趣的隨機集合。這樣的事情會讓招募人員對你更有興趣,並且讓其他網站追蹤你的人也能了解你的興趣! ![](https://thepracticaldev.s3.amazonaws.com/i/cc0my10l8i7bw6yqi4cn.png) 5. 發揮創意。更多激發您創造力的好點子: - 艾伯塔德沃爾 (Alberta Devor) 的火車路線靈感[網站](https://albertadevor.com/) ![](https://thepracticaldev.s3.amazonaws.com/i/m4kry69flr4l8t2kjq8i.png) - 像素獎得主 Maria Passo 製作的精美動畫[網站](http://marisapassos.com/) ![](https://thepracticaldev.s3.amazonaws.com/i/gazd8zmp2c4clff2u59b.png) - 加里·勒馬森 (Gary Le Masson)[網站](http://www.garylemasson.com/)上引人注目的搜尋引擎框 ![](https://thepracticaldev.s3.amazonaws.com/i/f406l7g0g7q05wzt1l63.png) - Kristine Flatland 格式有趣的[網站](http://kristineflat.land/#work2) ![](https://thepracticaldev.s3.amazonaws.com/i/e8ireutjnslih5n49vln.png) - 克萊門汀‧雅各比 (Clementine Jacoby) 繪製的她曾經造訪過的[網站](http://clementinejacoby.com/new_map.html)的地圖 ![](https://thepracticaldev.s3.amazonaws.com/i/le35y2tqg55xdsdt3yls.png) 在評論中分享在您的網站上對您有用的內容! --- 原文出處:https://dev.to/amandasopkin/fantastic-personal-websites-and-how-to-make-them--22om

Next.js 14 使用瀏覽器爬蟲,進行即時資料抓取的預訂應用程式

目錄 == - [介紹](#introduction) - [技術堆疊](#tech-stack) - [特徵](#features) - [設定 Next.js 應用程式](#step-1-setting-up-the-nextjs-application) - [安裝所需的套件](#step-2-installing-required-packages) - [設定 Redis 連接](#step-3-setting-up-redis-connection) - [配置 BullMQ 佇列](#step-4-configuring-bullmq-queue) - [Next.js 儀器設置](#step-5-nextjs-instrumentation-setup) - [設定 Bright Data 的抓取瀏覽器](#step-6-setting-up-bright-datas-scraping-browser) - [Bright Data 的抓取瀏覽器是什麼?](#what-is-bright-datas-scraping-browser) - [設定 Bright Data 抓取瀏覽器的步驟](#steps-to-set-up-bright-datas-scraping-browser) - [使用 Puppeteer 實作抓取邏輯](#implementing-the-scraping-logic-with-puppeteer) - [航班搜尋功能](#flight-search-feature) - [顯示航班搜尋結果](#displaying-flight-search-results) - [探索完整的指南和程式碼庫](#discover-the-complete-guide-and-codebase) - [在 YouTube 上觀看詳細說明](#watch-the-detailed-explanation-on-youtube) - [在 GitHub 上探索完整程式碼](#explore-the-full-code-on-github) - [結論](#conclusion) 介紹 == 在不斷發展的 Web 開發領域,有效收集、處理和顯示外部來源資料的能力變得越來越有價值。無論是市場研究、競爭分析或客戶洞察,網路抓取在釋放網路資料的巨大潛力方面都發揮著至關重要的作用。 這篇部落格文章介紹了建立強大的 Next.js 應用程式的綜合指南,該應用程式旨在從領先的旅行搜尋引擎之一 Kayak 抓取航班資料。透過利用 Next.js 的強大功能以及 BullMQ、Redis 和 Puppeteer 等現代技術。 技術堆疊 ==== - [Next.js](https://nextjs.org/docs) - [順風CSS](https://tailwindcss.com/docs) - [下一個介面](https://nextui.org/docs) - [健康)狀況](https://zustand.surge.sh/) - [條紋](https://stripe.com/docs) - [Bright Data 的抓取瀏覽器](https://brdta.com/kishansheth21) - [打字稿](https://www.typescriptlang.org/docs) - [雷迪斯](https://redis.io/documentation) - [BullMQ](https://docs.bullmq.io/) - [傀儡師](https://pptr.dev/) - [智威湯遜](https://jwt.io/introduction) - [阿克西奧斯](https://axios-http.com/docs/intro) - [PostgreSQL](https://www.postgresql.org/docs) - [棱鏡](https://www.prisma.io/docs) 特徵 == - 🚀 帶有 Tailwind CSS 的 Next.js 14 應用程式目錄 - 體驗由最新 Next.js 14 提供支援的時尚現代的 UI,並使用 Tailwind CSS 進行設計,以實現完美的外觀和感覺。 - 🔗 API 路由和伺服器操作 - 深入研究與 Next.js 14 的 API 路由和伺服器操作的無縫後端集成,確保高效的資料處理和伺服器端邏輯執行。 - 🕷 使用 Puppeteer Redis 和 BullMQ 進行抓取 - 利用 Puppeteer 的強大功能進行進階 Web 抓取,並使用 Redis 和 BullMQ 管理佇列和作業以實現強大的後端操作。 - 🔑 用於身份驗證和授權的 JWT 令牌 - 使用 JWT 令牌保護您的應用程式,為整個平台提供可靠的身份驗證和授權方法。 - 💳 支付網關 Stripe - 整合 Stripe 進行無縫支付處理,為預訂旅行、航班和飯店提供安全、輕鬆的交易。 - ✈️ 使用 Stripe 支付網關預訂旅行、航班和飯店 - 使用我們的 Stripe 支援的支付系統,讓您的旅遊預訂體驗變得輕鬆。 - 📊 從多個網站抓取即時資料 - 從多個來源抓取即時資料,保持領先,讓您的應用程式更新最新資訊。 - 💾 使用 Prisma 將抓取的資料儲存在 PostgreSQL 中 - 利用 PostgreSQL 和 Prisma 高效儲存和管理抓取的資料,確保可靠性和速度。 - 🔄 用於狀態管理的 Zustand - 透過 Zustand 簡化狀態邏輯並增強效能,在您的應用程式中享受流暢且可管理的狀態管理。 - 😈 該應用程式的最佳功能 - 使用 Bright Data 的抓取瀏覽器抓取不可抓取的資料。 ![抓取瀏覽器迷因](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0ytki1wpsbvluk1qkpo.jpg) Bright Data的抓取瀏覽器為我們提供了自動驗證碼解決功能,可以幫助我們抓取不可抓取的資料。 第 1 步:設定 Next.js 應用程式 --------------------- 1. **建立 Next.js 應用程式**:首先建立一個新的 Next.js 應用程式(如果您還沒有)。您可以透過在終端機中執行以下命令來完成此操作: ``` npx create-next-app@latest booking-app ``` 2. **導航到您的應用程式目錄**:變更為您新建立的應用程式目錄: ``` cd booking-app ``` 步驟2:安裝所需的軟體包 ------------ 您需要安裝多個軟體包,包括 Redis、BullMQ 和 Puppeteer Core。執行以下命令來安裝它們: ``` npm install ioredis bullmq puppeteer-core ``` - `ioredis`是 Node.js 的強大 Redis 用戶端,支援與 Redis 進行通訊。 - `bullmq`以 Redis 作為後端來管理作業和訊息佇列。 - `puppeteer-core`可讓您控制外部瀏覽器以進行抓取。 步驟3:設定Redis連接 ------------- 在適當的目錄(例如`lib/` )中建立一個檔案(例如`redis.js` )來配置 Redis 連線: ``` // lib/redis.js import Redis from 'ioredis'; // Use REDIS_URL from environment or fallback to localhost const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; const connection = new Redis(REDIS_URL); export { connection }; ``` 步驟4:配置BullMQ佇列 -------------- 透過在 Redis 配置所在的相同目錄中建立另一個檔案(例如, `queue.js` )來設定 BullMQ 佇列: ``` // lib/queue.js import { Queue } from 'bullmq'; import { connection } from './redis'; export const importQueue = new Queue('importQueue', { connection, defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 5000, }, }, }); ``` 第 5 步:Next.js 儀器設置 ------------------ Next.js 允許偵測,可以在 Next.js 配置中啟用。您還需要建立一個用於作業處理的工作文件。 1.**在 Next.js 中啟用 Instrumentation** :將以下內容新增至`next.config.js`以啟用 Instrumentation: ``` // next.config.js module.exports = { experimental: { instrumentationHook: true, }, }; ``` 2.**建立用於作業處理的 Worker** :在您的應用程式中,建立一個檔案 ( `instrumentation.js` ) 來處理作業處理。該工作人員將使用 Puppeteer 來執行抓取任務: ``` // instrumentation.js export const register = async () => { if (process.env.NEXT_RUNTIME === 'nodejs') { const { Worker } = await import('bullmq'); const puppeteer = await import('puppeteer-core'); const { connection } = await import('./lib/redis'); const { importQueue } = await import('./lib/queue'); new Worker('importQueue', async (job) => { // Job processing logic with Puppeteer goes here }, { connection, concurrency: 10, removeOnComplete: { count: 1000 }, removeOnFail: { count: 5000 }, }); } }; ``` 第 6 步:設定 Bright Data 的抓取瀏覽器 --------------------------- 在設定 Bright 資料抓取瀏覽器之前,我們先來談談什麼是抓取瀏覽器。 ### Bright Data 的抓取瀏覽器是什麼? Bright Data 的抓取瀏覽器是一款用於自動網頁抓取的尖端工具,旨在與 Puppeteer、Playwright 和 Selenium 無縫整合。它提供了一套網站解鎖功能,包括代理輪換、驗證碼解決等,以提高抓取效率。它非常適合需要互動的複雜網頁抓取,透過在 Bright Data 基礎架構上託管無限的瀏覽器會話來實現可擴展性。如欲了解更多詳情,請造訪[光明資料](https://brdta.com/kishansheth21)。 ![明亮的資料抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c70ochb1lgdrusgicwz4.png) <a id="steps-to-set-up-bright-datas-scraping-browser"></a> #### 第 1 步:導覽至 Bright Data 網站 首先造訪[Brightdata.com](https://brdta.com/kishansheth21) 。這是您存取 Bright Data 提供的豐富網頁抓取資源和工具的入口。 ![光明資料首頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afgkpgytcytoqfuq0h8w.png) #### 第 2 步:建立帳戶 造訪 Bright Data 網站後,註冊並建立一個新帳戶。系統將提示您輸入基本資訊以啟動並執行您的帳戶。 ![登入/註冊 Bright Data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffha75szs9tubh8kra9j.png) #### 第 3 步:選擇您的產品 在產品選擇頁面上,尋找代理商和抓取基礎設施產品。本產品專為滿足您的網路抓取需求而設計,提供強大的資料擷取工具和功能。 ![光明資料產品](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b058azlmjv30po6289fh.png) #### 第 4 步:新增代理 在「代理程式和抓取基礎設施」頁面中,您會找到一個「新增按鈕」。點擊此按鈕開始將新的抓取瀏覽器新增到您的工具包的過程。 ![新代理](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jscxsyt9yess1nvzi4z.png) #### 第五步:選擇抓取瀏覽器 將出現一個下拉列表,您應該從中選擇抓取瀏覽器選項。這告訴 Bright Data 您打算設定一個新的抓取瀏覽器環境。 ![選擇抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i483ipx7spwne65c6tep.png) #### 第 6 步:為您的抓取瀏覽器命名 為您的新抓取瀏覽器指定一個唯一的名稱。這有助於稍後辨識和管理它,特別是如果您計劃對不同的抓取專案使用多個瀏覽器。 ![抓取瀏覽器名稱](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0qnitec7s7gki7t3826.png) #### 步驟7:新增瀏覽器 命名您的瀏覽器後,按一下「新增」按鈕。此操作完成了新的抓取瀏覽器的建立。 ![新增抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ao6n1edyyx2no621nh01.png) #### 第 8 步:查看您的抓取瀏覽器詳細訊息 新增抓取瀏覽器後,您將被導向到一個頁面,您可以在其中查看新建立的抓取瀏覽器的所有詳細資訊。這些資訊對於整合和使用至關重要。 ![抓取瀏覽器詳細訊息](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ev33mbgznevn6h9p60g6.png) #### 第 9 步:存取程式碼和整合範例 尋找“查看程式碼和整合範例”按鈕。點擊此按鈕將為您提供如何跨多種程式語言和程式庫整合和使用抓取瀏覽器的全面視圖。對於希望自訂抓取設定的開發人員來說,此資源非常寶貴。 ![程式碼和整合範例按鈕](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30araqzko585yzmhrumd.png) #### 第 10 步:整合您的抓取瀏覽器 最後,複製 SRS\_WS\_ENDPOINT 變數。這是一條關鍵訊息,您需要將其整合到原始程式碼中,以便您的應用程式能夠與您剛剛設定的抓取瀏覽器進行通訊。 ![抓取瀏覽器端點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqhpglz1lngobguk2nu4.png) 透過遵循這些詳細步驟,您已在 Bright Data 平台中成功建立了一個抓取瀏覽器,準備好處理您的網頁抓取任務。請記住,Bright Data 提供廣泛的文件和支持,幫助您最大限度地提高抓取專案的效率和效果。無論您是在收集市場情報、進行研究還是監控競爭格局,新設定的抓取瀏覽器都是資料收集庫中的強大工具。 ### 第 7 步:使用 Puppeteer 實作抓取邏輯 從我們上次設定用於抓取航班資料的 Next.js 應用程式的地方開始,下一個關鍵步驟是實現實際的抓取邏輯。此過程涉及利用 Puppeteer 連接到瀏覽器實例、導航到目標 URL(在我們的範例中為 Kayak)並抓取必要的飛行資料。提供的程式碼片段概述了實現此目標的複雜方法,與我們先前建立的 BullMQ 工作設定無縫整合。讓我們分解這個抓取邏輯的元件,並了解它如何適合我們的應用程式。 #### 建立與瀏覽器的連接 我們抓取過程的第一步是透過 Puppeteer 建立與瀏覽器的連線。這是透過利用`puppeteer.connect`方法來完成的,該方法使用 WebSocket 端點 ( `SBR_WS_ENDPOINT` ) 連接到現有的瀏覽器實例。此環境變數應設定為您正在使用的抓取瀏覽器服務的 WebSocket URL,例如 Bright Data: ``` const browser = await puppeteer.connect({ browserWSEndpoint: SBR_WS_ENDPOINT, }); ``` #### 開啟新頁面並導航到目標 URL 連線後,我們在瀏覽器中建立一個新頁面並導航到作業資料中指定的目標 URL。此 URL 是我們打算從中抓取航班資料的特定 Kayak 搜尋結果頁面: ``` const page = await browser.newPage(); await page.goto(job.data.url); ``` #### 抓取航班資料 我們邏輯的核心在於從頁面中抓取航班資料。我們透過使用`page.evaluate`來實現這一點,這是一種 Puppeteer 方法,允許我們在瀏覽器上下文中執行腳本。在此腳本中,我們等待必要的元素加載,然後繼續收集航班資訊: - **Flight Selector** :我們以`.nrc6-wrapper`類別為目標元素,其中包含航班詳細資訊。 - **資料擷取**:對於每個航班元素,我們提取詳細訊息,例如航空公司徽標、出發和到達時間、航班持續時間、航空公司名稱和價格。出發和到達時間經過清理,以刪除最後不必要的數值,確保我們準確地捕捉時間。 - **價格處理**:價格在刪除所有非數字字元後提取為整數,確保其可用於數值運算或比較。 擷取的資料被建構成飛行物件陣列,每個物件都包含上述詳細資訊: ``` const scrappedFlights = await page.evaluate(async () => { // Data extraction logic const flights = []; // Process each flight element // ... return flights; }); ``` #### 錯誤處理和清理 我們的抓取邏輯被包裝在一個 try-catch 區塊中,以在抓取過程中優雅地處理任何潛在的錯誤。無論結果如何,我們都會確保瀏覽器在finally區塊中正確關閉,從而保持資源效率並防止潛在的記憶體洩漏: ``` try { // Scraping logic } catch (error) { console.log({ error }); } finally { await browser.close(); console.log("Browser closed successfully."); } ``` #### 整個程式碼 ``` const SBR_WS_ENDPOINT = process.env.SBR_WS_ENDPOINT; export const register = async () => { if (process.env.NEXT_RUNTIME === "nodejs") { const { Worker } = await import("bullmq"); const puppeteer = await import("puppeteer"); const { connection } = await import("./lib/redis"); const { importQueue } = await import("./lib/queue"); new Worker( "importQueue", async (job) => { const browser = await puppeteer.connect({ browserWSEndpoint: SBR_WS_ENDPOINT, }); try { const page = await browser.newPage(); console.log("in flight scraping"); console.log("Connected! Navigating to " + job.data.url); await page.goto(job.data.url); console.log("Navigated! Scraping page content..."); const scrappedFlights = await page.evaluate(async () => { await new Promise((resolve) => setTimeout(resolve, 5000)); const flights = []; const flightSelectors = document.querySelectorAll(".nrc6-wrapper"); flightSelectors.forEach((flightElement) => { const airlineLogo = flightElement.querySelector("img")?.src || ""; const [rawDepartureTime, rawArrivalTime] = ( flightElement.querySelector(".vmXl")?.innerText || "" ).split(" – "); // Function to extract time and remove numeric values at the end const extractTime = (rawTime: string): string => { const timeWithoutNumbers = rawTime .replace(/[0-9+\s]+$/, "") .trim(); return timeWithoutNumbers; }; const departureTime = extractTime(rawDepartureTime); const arrivalTime = extractTime(rawArrivalTime); const flightDuration = ( flightElement.querySelector(".xdW8")?.children[0]?.innerText || "" ).trim(); const airlineName = ( flightElement.querySelector(".VY2U")?.children[1]?.innerText || "" ).trim(); // Extract price const price = parseInt( ( flightElement.querySelector(".f8F1-price-text")?.innerText || "" ) .replace(/[^\d]/g, "") .trim(), 10 ); flights.push({ airlineLogo, departureTime, arrivalTime, flightDuration, airlineName, price, }); }); return flights; }); } catch (error) { console.log({ error }); } finally { await browser.close(); console.log("Browser closed successfully."); } }, { connection, concurrency: 10, removeOnComplete: { count: 1000 }, removeOnFail: { count: 5000 }, } ); } }; ``` ### 步驟8:航班搜尋功能 基於我們的航班資料抓取功能,讓我們將全面的航班搜尋功能整合到我們的 Next.js 應用程式中。此功能將為使用者提供一個動態介面,透過指定出發地、目的地和日期來搜尋航班。利用強大的 Next.js 框架以及現代 UI 庫和狀態管理,我們建立了引人入勝且響應迅速的航班搜尋體驗。 #### 航班搜尋功能的關鍵組成部分 1. **動態城市選擇**:此功能包括來源和目的地輸入的自動完成功能,由預先定義的城市機場程式碼清單提供支援。當使用者輸入時,應用程式會過濾並顯示匹配的城市,透過更輕鬆地尋找和選擇機場來增強用戶體驗。 2. **日期選擇**:使用者可以透過日期輸入選擇預期的航班日期,為規劃旅行提供彈性。 3. **抓取狀態監控**:啟動抓取作業後,應用程式透過定期 API 呼叫來監控作業的狀態。這種非同步檢查允許應用程式使用抓取過程的狀態更新 UI,確保使用者了解進度和結果。 #### 航班搜尋元件的完整程式碼 ``` "use client"; import { useAppStore } from "@/store"; import { USER_API_ROUTES } from "@/utils/api-routes"; import { cityAirportCode } from "@/utils/city-airport-codes"; import { Button, Input, Listbox, ListboxItem } from "@nextui-org/react"; import axios from "axios"; import Image from "next/image"; import { useRouter } from "next/navigation"; import React, { useEffect, useRef, useState } from "react"; import { FaCalendarAlt, FaSearch } from "react-icons/fa"; const SearchFlights = () => { const router = useRouter(); const { setScraping, setScrapingType, setScrappedFlights } = useAppStore(); const [loadingJobId, setLoadingJobId] = useState<number | undefined>(undefined); const [source, setSource] = useState(""); const [sourceOptions, setSourceOptions] = useState< { city: string; code: string; }[] >([]); const [destination, setDestination] = useState(""); const [destinationOptions, setDestinationOptions] = useState< { city: string; code: string; }[] >([]); const [flightDate, setFlightDate] = useState(() => { const today = new Date(); return today.toISOString().split("T")[0]; }); const handleSourceChange = (query: string) => { const matchingCities = Object.entries(cityAirportCode) .filter(([, city]) => city.toLowerCase().includes(query.toLowerCase())) .map(([code, city]) => ({ code, city })) .splice(0, 5); setSourceOptions(matchingCities); }; const destinationChange = (query: string) => { const matchingCities = Object.entries(cityAirportCode) .filter(([, city]) => city.toLowerCase().includes(query.toLowerCase())) .map(([code, city]) => ({ code, city })) .splice(0, 5); setDestinationOptions(matchingCities); }; const startScraping = async () => { if (source && destination && flightDate) { const data = await axios.get(`${USER_API_ROUTES.FLIGHT_SCRAPE}?source=${source}&destination=${destination}&date=${flightDate}`); if (data.data.id) { setLoadingJobId(data.data.id); setScraping(true); setScrapingType("flight"); } } }; useEffect(() => { if (loadingJobId) { const checkIfJobCompleted = async () => { try { const response = await axios.get(`${USER_API_ROUTES.FLIGHT_SCRAPE_STATUS}?jobId=${loadingJobId}`); if (response.data.status) { set ScrappedFlights(response.data.flights); clearInterval(jobIntervalRef.current); setScraping(false); setScrapingType(undefined); router.push(`/flights?data=${flightDate}`); } } catch (error) { console.log(error); } }; jobIntervalRef.current = setInterval(checkIfJobCompleted, 3000); } return () => clearInterval(jobIntervalRef.current); }, [loadingJobId]); return ( <div className="h-[90vh] flex items-center justify-center"> <div className="absolute left-0 top-0 h-[100vh] w-[100vw] max-w-[100vw] overflow-hidden overflow-x-hidden"> <Image src="/flight-search.png" fill alt="Search" /> </div> <div className="absolute h-[50vh] w-[60vw] flex flex-col gap-5"> {/* UI and functionality for flight search */} </div> </div> ); }; export default SearchFlights; ``` ### 步驟9:航班搜尋頁面UI ![航班搜尋頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54bkhpea27fkzg4vlur1.png) ### 顯示航班搜尋結果 成功抓取飛行資料後,下一個關鍵步驟是以使用者友善的方式將這些結果呈現給使用者。 Next.js 應用程式中的 Flights 元件就是為此目的而設計的。 ``` "use client"; import { useAppStore } from "@/store"; import { USER_API_ROUTES } from "@/utils/api-routes"; import { Button } from "@nextui-org/react"; import axios from "axios"; import Image from "next/image"; import { useRouter, useSearchParams } from "next/navigation"; import React from "react"; import { FaChevronLeft } from "react-icons/fa"; import { MdOutlineFlight } from "react-icons/md"; const Flights = () => { const router = useRouter(); const searchParams = useSearchParams(); const date = searchParams.get("date"); const { scrappedFlights, userInfo } = useAppStore(); const getRandomNumber = () => Math.floor(Math.random() * 41); const bookFLight = async (flightId: number) => {}; return ( <div className="m-10 px-[20vw] min-h-[80vh]"> <Button className="my-5" variant="shadow" color="primary" size="lg" onClick={() => router.push("/search-flights")} > <FaChevronLeft /> Go Back </Button> <div className="flex-col flex gap-5"> {scrappedFlights.length === 0 && ( <div className="flex items-center justify-center py-5 px-10 mt-10 rounded-lg text-red-500 bg-red-100 font-medium"> No Flights found. </div> )} {scrappedFlights.map((flight: any) => { const seatsLeft = getRandomNumber(); return ( <div key={flight.id} className="grid grid-cols-12 border bg-gray-200 rounded-xl font-medium drop-shadow-md" > <div className="col-span-9 bg-white rounded-l-xl p-10 flex flex-col gap-5"> <div className="grid grid-cols-4 gap-4"> <div className="flex flex-col gap-3 font-medium"> <div> <div className="relative w-20 h-16"> <Image src={flight.logo} alt="airline name" fill /> </div> </div> <div>{flight.name}</div> </div> <div className="col-span-3 flex justify-between"> <div className="flex flex-col gap-2"> <div className="text-blue-600">From</div> <div> <span className="text-3xl"> <strong>{flight.departureTime}</strong> </span> </div> <div>{flight.from}</div> </div> <div className="flex flex-col items-center justify-center gap-2"> <div className="bg-violet-100 w-max p-3 text-4xl text-blue-600 rounded-full"> <MdOutlineFlight /> </div> <div> <span className="text-lg"> <strong>Non-stop</strong> </span> </div> <div>{flight.duration}</div> </div> <div className="flex flex-col gap-2"> <div className="text-blue-600">To</div> <div> <span className="text-3xl"> <strong>{flight.arrivalTime}</strong> </span> </div> <div>{flight.to}</div> </div> </div> </div> <div className="flex justify-center gap-10 bg-violet-100 p-3 rounded-lg"> <div className="flex"> <span>Airplane  </span> <span className="text-blue-600 font-semibold"> Boeing 787 </span> </div> <div className="flex"> <span>Travel Class:  </span> <span className="text-blue-600 font-semibold">Economy</span> </div> </div> <div className="flex justify-between font-medium"> <div> Refundable <span className="text-blue-600"> $5 ecash</span> </div> <div className={`${ seatsLeft > 20 ? "text-green-500" : "text-red-500" }`} > Only {seatsLeft} Seats Left </div> <div className="cursor-pointer">Flight Details</div> </div> </div> <div className="col-span-3 bg-violet-100 rounded-r-xl h-full flex flex-col items-center justify-center gap-5"> <div> <div> <span className="line-through font-light"> ${flight.price + 140} </span> </div> <div className="flex items-center gap-2"> <span className="text-5xl font-bold">${flight.price}</span> <span className="text-blue-600">20% OFF</span> </div> </div> <Button variant="ghost" radius="full" size="lg" color="primary" onClick={() => { if (userInfo) bookFLight(flight.id); }} > {userInfo ? "Book Now" : "Login to Book"} </Button> </div> </div> ); })} </div> </div> ); }; export default Flights; ``` #### 航班搜尋結果 ![航班搜尋結果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxufusoyrf6hgtrrcmsw.png) ### 探索完整的指南和程式碼庫 上面共享的部分和程式碼片段僅代表使用 Next.js 建立強大的航班資料抓取和搜尋應用程式所需的完整功能和程式碼的一小部分。為了掌握這個專案的全部內容,包括高級功能、優化和最佳實踐,我邀請您更深入地研究我的線上綜合資源。 #### 在 YouTube 上觀看詳細說明 有關引導您完成此應用程式的開發過程、編碼細微差別和功能的逐步影片指南,請觀看我的 YouTube 影片。本教程旨在讓您更深入地了解這些概念,讓您按照自己的步調進行操作並獲得對 Next.js 應用程式開發的寶貴見解。 https://www.youtube.com/watch?v=ZWVhk0fxHM0 #### 在 GitHub 上探索完整程式碼 如果您渴望探索完整的程式碼,請造訪我的 GitHub 儲存庫。在那裡,您將找到完整的程式碼庫,包括讓該應用程式在您自己的電腦上執行所需的所有元件、實用程式和設定說明。 https://github.com/koolkishan/nextjs-travel-planner ### 結論 使用 Next.js 建立飛行資料抓取和搜尋工具等綜合應用程式展示了現代 Web 開發工具和框架的強大功能和多功能性。無論您是希望提高技能的經驗豐富的開發人員,還是渴望深入 Web 開發的初學者,這些資源都是為您的旅程量身定制的。在 YouTube 上觀看詳細教程,在 GitHub 上探索完整程式碼,並加入對話以增強您的開發專業知識並為充滿活力的開發者社群做出貢獻。 --- 原文出處:https://dev.to/kishansheth/nextjs-14-booking-app-with-live-data-scraping-using-scraping-browser-610

適用於 Windows 10 的最佳免費 terminal

如果您嘗試在 PC 上使用終端,我對您的痛苦表示歉意。以下是一些可以提高您的工作流程的最佳終端模擬器: ### [1.Cmder](https://cmder.net//) ![Cmder終端鏡像](https://thepracticaldev.s3.amazonaws.com/i/q9a46hoblnd4mq48hhz0.png) [Cmder](https://cmder.net//)是一款便攜式控制台模擬器,基於已經流行的[Conemu](https://conemu.github.io/)建置; Conemu 也值得一試,因為它可以作為 Cmder 的替代品。 Cmders網站對此解釋得非常完美: > 將 cmder 更多地視為一個軟體包,而不是一個單獨的應用程式。所有的魔力都是透過 ConEmu 發生的。透過[Clink](https://mridgers.github.io/clink/)的增強。 儘管 Cmder 有時會出現速度問題(儘管建議它是便攜式的),但它仍然是一個很好的基本控制台模擬器,可以用來實現您的目標。 ### [2.Hyper.is](https://hyper.is/) ![超級終端神奇寶貝主題](https://thepracticaldev.s3.amazonaws.com/i/ltxa2x4fakh4vtn3iqj3.PNG) [Hyper](https://hyper.is/)是一款時尚的終端,可在 PC 和 MAC 上使用,並且在主題、插件和 shell 方面是完全可自訂的。由於我在安裝[zsh shell](https://github.com/robbyrussell/oh-my-zsh)時遇到了一些錯誤,因此在開發方面似乎仍然有很多工作要做。不管怎樣,它都是一個值得研究的出色的可擴展終端。 (注意:如果您想選擇 Pokemon 主題,可以[在這裡獲取!](https://github.com/klaussinani/hyper-pokemon) ) ### [3. terminus](https://eugeny.github.io/terminus/) ![terminus](https://res.cloudinary.com/tangobango/image/upload/v1565020537/terminus_2.0_hnhplq.png) 「現代的航站」是總站的口號。該終端看起來類似於開發環境,它帶有分割窗格、完全可配置的快捷方式、選項卡以及對所有主要 shell 的支援。這個航站樓仍然是新的,接下來會發生什麼將會令人興奮。 ### [4. FluentTerminal](https://github.com/felixse/FluentTerminal) ![FluentTerminal](https://res.cloudinary.com/tangobango/image/upload/v1565492456/Dev.to%20Articles/Best%20Standalone%20Terminals%202019/fluent-terminal_qfyrna.jpg) 適用於PowerShell、CMD、WSL 或自訂shell 的終端,甚至可以從iTerm 獲取主題,並具有可編輯的鍵綁定和快速可調整的設置,[Fluent](https://github.com/felixse/FluentTerminal) 絕對值得一試。 ### [5. alacritty](https://github.com/jwilm/alacritty) ![alacritty 終端](https://res.cloudinary.com/tangobango/image/upload/v1565493826/Dev.to%20Articles/Best%20Standalone%20Terminals%202019/2ebd0288-d06c-11e6-95d3-4a2889dbbd6f_xvc26b.png) 這個有點爭議。 Alacritty 聲稱自己是「現有最快的終端模擬器」。雖然這[可能不完全正確,](https://news.ycombinator.com/item?id=16936181)但許多用戶報告使用 Alacritty 時速度很快,但使用速度很低。因此,儘管它可能不適合所有人,但它也許能夠解決您在終端上遇到的一些滯後/撕裂問題。 這篇文章是否有幫助或想要加入一些內容/提出問題?請隨意 [connect](https://dev.to/connect) 並[關注](https://dev.to/adnanmostafa)以了解更多資訊或在下面發表評論! --- 原文出處:https://dev.to/adnanmostafa/the-best-free-standalone-terminals-for-windows-2019-kmj

Rust 中的 Laravel?我這樣做是有原因的。

大家都怎麼啦! 過去幾個月我一直在研究 Rust,並且總是試圖接近 Laravel 環境中的實際堆疊。 對於我來說,作為一名 PHP 開發人員,跳入 Rust 確實是一項艱鉅的任務,因為我以前從未接觸過函數式編程,但是我找到了一種方法讓這變得「更容易」。 銹據我所知 ----- 整個生態系統由不同的套件組成,您應該根據需要加入它,這與 Laravel 不同,Laravel 圍繞框架有一個非常強大的環境。 所以我的第一印像是我必須學習如何使用基礎包: - 多滕維 - 時空 - 時間 - Uuid - 等等。 但是當涉及到 Web 開發(主要是 API)時,您必須從眾多現有框架中選擇一個。喜歡: - Actix(目前使用) - 阿克蘇姆(評估中) - Rocket(人們總是告訴我不要使用它,仍然不知道為什麼) 而且它們都沒有 Laravel 那種固執己見的“結構”,所以我決定建立我的。 Rust 中的 Laravel --------------- 我為什麼要談 Laravel?因為它為我們提供的“MVC”結構對於使用 Rust Web 的小型專案來說足夠優雅。 當我使用固定的 Laravel 框架編寫 Rust 程式碼時,事情開始變得有意義。這是我正在談論的內容的結構: ``` ./src ├── app.rs ├── config.rs ├── http │   ├── controllers │   │   ├── leaderboard_controller.rs │   │   ├── mod.rs │   │   └── submissions_controller.rs │   ├── mod.rs │   └── requests │   ├── leaderboard_request.rs │   ├── mod.rs │   └── submission_request.rs ├── main.rs ├── models │   ├── leaderboard.rs │   ├── mod.rs │   └── submission.rs └── repositories ├── leaderboard_repository.rs ├── mod.rs └── submission_repository.rs ``` 該專案目前正在使用中: - [阿克泰克斯](https://actix.rs/docs/) - [卡律布狄斯 ORM](https://github.com/nodecosmos/charybdis) 你可以在這個[Pull Request](https://github.com/DanielHe4rt/leaderboard-rust/pull/1)中檢查我的混亂情況 問題是:在使用 Rust 時遵循這個想法會是一件好事嗎?簡單性和良好的維護結構是我在專案中一直追求的目標。 另外,我正在考慮寫一些關於我的流程如何從 PHP 遷移到 Rust 的系列文章,您有興趣閱讀類似的內容嗎? 希望在這裡看到您的想法,謝謝! --- 原文出處:https://dev.to/danielhe4rt/laravel-inside-rust-i-have-a-reason-for-that-ke3

6 個 JavaScript 控制台方法,例如泰勒絲民間傳說歌詞

--- 標題:6 個 JavaScript 控制台方法,如泰勒絲民間傳說歌詞 發表:真實 描述:了解六種鮮為人知的 JavaScript 控制台方法,這些方法與泰勒絲 (Taylor Swift) 最新專輯《Followre》中的歌詞類似。 標籤: javascript、webdev、除錯、生產力 封面圖片:https://twilio-cms-prod.s3.amazonaws.com/images/ts\_AMVvYGp.width-1616.png --- 這篇部落格文章是為[Twilio](https://www.twilio.com/)撰寫的,[最初發佈在 Twilio 部落格上](https://www.twilio.com/blog/js-console-methods-like-taylor-swift-folklore-lyrics)。 如果您進行 Web 開發,您可能至少使用過一次`console.log` (或超過一千次...誰在數?),因為這是最好的偵錯方法!但您知道還有其他控制台方法嗎? Taylor Swift 的最新專輯《Followre》 充滿了沉思的隱喻、典故和象徵意義,這篇文章將其中一些歌詞比作 6 個鮮為人知的 JavaScript `console`方法。 ### 控制台到底是什麼? `console`是一個[全域物件](https://developer.mozilla.org/en-US/docs/Glossary/Global_object),允許開發人員存取偵錯控制台。它有大量的方法,可以更輕鬆地記錄語句、變數、函數、錯誤等——天哪! #### 6種類似民間傳說歌詞的控制台方法 #### *1. console.log = "但這會很有趣 // 如果你是那個人"* `console.log`是最常用的方法。用於通用日誌記錄,它在 Web 控制台中顯示傳遞給它的訊息。你知道你可以用 CSS 來裝飾它嗎? ``` console.log("%cWARNING: you will be obsessed with folklore", "font: 2em sans-serif; color: yellow; background-color: red;"); ``` ![輸出console.log css](https://twilio-cms-prod.s3.amazonaws.com/images/Yz6zzAsmqMlZsQPirgqJZ1U1MWR_pPvMUExuwPavf-Orm.width-1000.png) `Log`簡單、可靠,並且可以完成工作,但它被過度使用,吸引了類似`console`方法的所有註意力,這些方法做得更多。如果`Log`是您需要的一種或唯一的控制台方法,那麼它會很有趣 - 但正如本文將要展示的,您將透過其他`console`方法獲得更多樂趣! #### *2. console.table = "我是一個鏡球 // 今晚我會向你展示你自己的每個版本"* `table`方法接受一個物件或陣列並將輸入記錄為表格,使其看起來更乾淨:它就像`log`的更好版本。與鏡像球一樣, `table`可以透過接受可選參數`columns`來選擇要顯示的列子集來顯示不同版本的輸入。 陣列中的每個元素(如果資料是物件,則為每個可枚舉屬性)將是表中的一行。下面的 JavaScript 程式碼有一個物件,您可以看到最初使用 log 的輸出。 ``` function Album(name, year, numSongs) { this.name = name; this.year = year; this.numSongs = numSongs; } const fearless = new Album("Fearless", 2008, 13); const speakNow = new Album("Speak Now", 2010, 19); const folklore = new Album("folklore", 2020, 16); console.log([fearless, speakNow, folklore]); ``` ![物件的console.log](https://twilio-cms-prod.s3.amazonaws.com/images/4w8wxLh0fE0C0YQ1-lO-MdqRmLx_QjFj-ng-UcbM-C8Q2.width-1000.png) 這很好,但是給定陣列時`table`的輸出看起來更好: ``` console.table([fearless, speakNow, folklore]); ``` ![物件的console.table](https://twilio-cms-prod.s3.amazonaws.com/images/MqAnPKv4jrbZVF4Z7-vf37jkzC6N4ppYKS_-pFH0eXuov.width-1000.png) 接受`columns`參數,如`console.table([fearless, speakNow, folklore], ["name"]);`會顯示: ![帶有額外列參數的 console.table](https://twilio-cms-prod.s3.amazonaws.com/images/UTQQlcite2SYJZMXWuKc_p0yetEku7jPZLGtsmBc5aTGY.width-1000.png) 您也可以傳遞它(而不是`name` ) `year`或`numSongs` - 就像鏡子球一樣,表格可以向您顯示其輸入的每個版本! #### *3. console.assert =“如果你從不流血,你就永遠不會成長”* `console.assert(expression, message)`僅在表達式為 false 時才列印。泰勒絲 (Taylor Swift) 在歌曲*《The 1》*中的歌詞「如果你從不流血,你就永遠不會成長」也同意這一點——如果你從不流血,或者失敗,或者有時不正確,你就永遠不會成長。 `assert`表明,透過錯誤,您可以成長為一名開發人員,因為您可以修復錯誤,控制台會透過將斷言變成漂亮的紅色來幫助您。 ``` const numFolkloreSongs = 16; const num1989Songs = 13; console.assert(numFolkloreSongs > num1989Songs, 'folklore has more songs than 1989'); //won't run console.assert(num1989Songs + 3 > numFolkloreSongs, 'the number of songs on 1989 + 3 is not greater than the number of folklore songs'); ``` ![斷言失敗](https://twilio-cms-prod.s3.amazonaws.com/images/6kyCsGK8UWI0nEEgZ703RHjPu67krRq-OCmTgkDuiJtYw.width-1000.png) #### *4. console.time/console.timeEnd = "時間,神秘的時間/切開我,然後治癒我。"* `console.time()`建立一個計時器來查看某些操作需要多長時間。它可以採用名稱或標籤的可選參數來區分網頁上最多 10,000 個計時器。 `console.timeEnd()`停止計時器,並在控制台中顯示結果。 時間可能很艱難——它可以讓你心碎,但它也可以治癒你,讓你感覺更好。 ``` console.time('sms timer'); let x = 0; while (x < 3) { console.log("They told me all of my cages were mental/So I got wasted like all my potential"); x+=1; } console.timeEnd('sms timer'); ``` ![控制台時間](https://twilio-cms-prod.s3.amazonaws.com/images/C0ExSxgVLyLR6uuPM911Lhw64QJ3qLZXDWKbrLdJ5FwnD.width-1000.png) 如果沒有標籤傳遞給`console.time()` ,它將記錄 default 而不是*sms time* 。 #### *5. console.clear:“如果我對你來說已經死了,為什麼你還在守靈?”* `console.clear`簡短、甜蜜、簡潔。它會清除控制台,並且在某些環境中,可能會列印諸如“控制台已清除”之類的訊息。 歌詞“如果我對你來說已經死了,為什麼你還在守靈?”是憂鬱的,但也有一些尖銳的味道:它非常適合當你想要結束對話時,就像`clear`一樣,你可以重新開始,重新開始。 #### *6. console.group/console.groupEnd ="想到一直以來有一條看不見的繩子把你和我綁在一起,這不是很美好嗎?"* `console.group`表示內聯訊息群組的開始, `console.groupEnd`標記其結束。如果群組包含日誌,它們將作為一個群組列印,因此格式更清晰,您可以更輕鬆地了解群組包含的內容。 就像有一些看不見的字串(或`console`命令)將群組中的專案捆綁在一起。 ``` console.group("folklore"); console.log("the 1"); console.log("cardigan"); console.log("the last great american dynasty"); console.log("invisible string"); console.log("my tears ricochet"); console.groupEnd(); console.log("outside"); ``` ![團體](https://twilio-cms-prod.s3.amazonaws.com/images/JiqlDPAtBRz7G_ExfwKLEhsOXwAMZKIRS-Bv3YaL9SKGb.width-1000.png) #### 控制台的下一步是什麼? ![tswift 太棒了 gif](https://twilio-cms-prod.s3.amazonaws.com/original_images/tsgif.gif) 還有許多其他控制台方法未包含在此處(部分原因是它們與 Taylor Swift 歌詞的關係不大。)有關控制台方法的更多訊息,[請查看 Mozilla 開發者網絡有關 Web 技術的文件](https://developer.mozilla.org/en-US/docs/Web/API/console)。讓我知道您最喜歡或最不喜歡的民俗歌曲在線或在評論中! 1. 推特: [@lizziepika](https://twitter.com/lizziepika) 2. GitHub:[伊莉莎白西格爾](https://github.com/elizabethsiegle) 3. 電子郵件:[email protected] --- 原文出處:https://dev.to/twilio/6-javascript-console-methods-like-taylor-swift-folklore-lyrics-3h0k

真正去打造一樣幫助他人前進的東西,就不會失去動力

看到站長翻譯的[Ruby on Rails 之父:我怎麼學會寫程式的?](https://codelove.tw/@howtomakeaturn/post/k312Ya)這篇文章,想發表一點想法。 ## 真正的學習 我不是本科出身的,很長一段時間我都是亂無章法的自學。報名了一堆線上課程,也請過家教,甚至一度想要參加訓練營。但我一直都知道,那不適合我。 從架設第一個網站開始,我就有很「明確的問題」需要解決,所以一路上我幾乎都是「做中學」,問題在於,無人可以「糾正」我的錯誤,而錯誤不糾正,就難以進步。 所以我意識到我需要的不是尋找更好的課程,而是一個教練,一個可以糾正我的錯誤並引導我的教練,而不是手把手的教員。 教練很重要,教練的品格更重要,我無法把一個人的專業和他的品格分開(我找過不良品)。有些事情不能妥協,所以我才找到阿川。 DHH寫的這篇文章很簡短,我相信有很多「前後文」沒有提到。每個人的人生都有前後文,我們來自不同背景,或正在經歷不同的處境,我們都不是從零開始。 我們都被那些「學習神話」給洗腦了,如果有一種方式可以學會任何東西,那我想就是你那顆向學的「心」,而不是那些看起來很完美的學習路徑。 ## 為了重新學習美語發音,我開發了一個小工具 我正在重新學習美語發音,為了幫助和我一樣正在學習的人更好的練習,我開發了一個輔助的小工具,並藉此機會學習 JavaScript 這個程式語言。 這個小工具是根據[蕭博士](https://www.facebook.com/wen.hsiao.100)研發設計的PA注音符號表所建立,附上這張表的連結:https://drive.google.com/file/d/1mbxaL4yTAcJvWREeN179DAeQMgC2yCuK/view?usp=sharing 小工具中的注音符號卡,就是根據這張表的子音與母音的搭配所產生,我只是打亂出現的順序,作為學習者練習的輔助工具。下面附上這個小程式的連結: https://joyho37.github.io/pa-practice-tool/ 歡迎各位前輩或同儕抓蟲或給我建議,我們一起學習。 - - - 我相信把心放到你的程式裡,就會產生動力。

為初學者到專家提供的 101 個 Bash 指令和提示

> **2019 年 9 月 25 日更新:**感謝[ラナ・kuaru](https://twitter.com/rana_kualu)的辛勤工作,本文現已提供日文版。請點擊下面的連結查看他們的工作。如果您知道本文被翻譯成其他語言,請告訴我,我會將其發佈在這裡。 [🇯🇵 閱讀日語](https://qiita.com/rana_kualu/items/7b62898d373901466f5c) > **2019 年 7 月 8 日更新:**我最近發現大約兩年前發佈在法語留言板上的[這篇非常相似的文章](https://bookmarks.ecyseo.net/?EAWvDw)。如果您有興趣學習一些 shell 命令——並且您*會說 français* ,那麼它是對我下面的文章的一個很好的補充。 直到大約一年前,我幾乎只在 macOS 和 Ubuntu 作業系統中工作。在這兩個作業系統上, `bash`是我的預設 shell。在過去的六、七年裡,我對`bash`工作原理有了大致的了解,並想為那些剛入門的人概述一些更常見/有用的命令。如果您認為您了解有關`bash`所有訊息,請無論如何看看下面的內容 - 我已經提供了一些提示和您可能忘記的標誌的提醒,這可以讓您的工作更輕鬆一些。 下面的命令或多或少以敘述風格排列,因此如果您剛開始使用`bash` ,您可以從頭到尾完成操作。事情到最後通常會變得不那麼常見並且變得更加困難。 <a name="toc"></a> 目錄 -- - [基礎](#the-basics) ``` - [First Commands, Navigating the Filesystem](#first-commands) ``` ``` - [`pwd / ls / cd`](#pwd-ls-cd) ``` ``` - [`; / && / &`](#semicolon-andand-and) ``` ``` - [Getting Help](#getting-help) ``` ``` - [`-h`](#minus-h) ``` ``` - [`man`](#man) ``` ``` - [Viewing and Editing Files](#viewing-and-editing-files) ``` ``` - [`head / tail / cat / less`](#head-tail-cat-less) ``` ``` - [`nano / nedit`](#nano-nedit) ``` ``` - [Creating and Deleting Files and Directories](#creating-and-deleting-files) ``` ``` - [`touch`](#touch) ``` ``` - [`mkdir / rm / rmdir`](#mkdir-rm-rmdir) ``` ``` - [Moving and Copying Files, Making Links, Command History](#moving-and-copying-files) ``` ``` - [`mv / cp / ln`](#mv-cp-ln) ``` ``` - [Command History](#command-history) ``` ``` - [Directory Trees, Disk Usage, and Processes](#directory-trees-disk-usage-processes) ``` ``` - [`mkdir –p / tree`](#mkdir--p-tree) ``` ``` - [`df / du / ps`](#df-du-ps) ``` ``` - [Miscellaneous](#basic-misc) ``` ``` - [`passwd / logout / exit`](#passwd-logout-exit) ``` ``` - [`clear / *`](#clear-glob) ``` - [中間的](#intermediate) ``` - [Disk, Memory, and Processor Usage](#disk-memory-processor) ``` ``` - [`ncdu`](#ncdu) ``` ``` - [`top / htop`](#top-htop) ``` ``` - [REPLs and Software Versions](#REPLs-software-versions) ``` ``` - [REPLs](#REPLs) ``` ``` - [`-version / --version / -v`](#version) ``` ``` - [Environment Variables and Aliases](#env-vars-aliases) ``` ``` - [Environment Variables](#env-vars) ``` ``` - [Aliases](#aliases) ``` ``` - [Basic `bash` Scripting](#basic-bash-scripting) ``` ``` - [`bash` Scripts](#bash-scripts) ``` ``` - [Custom Prompt and `ls`](#custom-prompt-ls) ``` ``` - [Config Files](#config-files) ``` ``` - [Config Files / `.bashrc`](#config-bashrc) ``` ``` - [Types of Shells](#types-of-shells) ``` ``` - [Finding Things](#finding-things) ``` ``` - [`whereis / which / whatis`](#whereis-which-whatis) ``` ``` - [`locate / find`](#locate-find) ``` ``` - [Downloading Things](#downloading-things) ``` ``` - [`ping / wget / curl`](#ping-wget-curl) ``` ``` - [`apt / gunzip / tar / gzip`](#apt-gunzip-tar-gzip) ``` ``` - [Redirecting Input and Output](#redirecting-io) ``` ``` - [`| / > / < / echo / printf`](#pipe-gt-lt-echo-printf) ``` ``` - [`0 / 1 / 2 / tee`](#std-tee) ``` - [先進的](#advanced) ``` - [Superuser](#superuser) ``` ``` - [`sudo / su`](#sudo-su) ``` ``` - [`!!`](#click-click) ``` ``` - [File Permissions](#file-permissions) ``` ``` - [File Permissions](#file-permissions-sub) ``` ``` - [`chmod / chown`](#chmod-chown) ``` ``` - [User and Group Management](#users-groups) ``` ``` - [Users](#users) ``` ``` - [Groups](#groups) ``` ``` - [Text Processing](#text-processing) ``` ``` - [`uniq / sort / diff / cmp`](#uniq-sort-diff-cmp) ``` ``` - [`cut / sed`](#cut-sed) ``` ``` - [Pattern Matching](#pattern-matching) ``` ``` - [`grep`](#grep) ``` ``` - [`awk`](#awk) ``` ``` - [Copying Files Over `ssh`](#ssh) ``` ``` - [`ssh / scp`](#ssh-scp) ``` ``` - [`rsync`](#rsync) ``` ``` - [Long-Running Processes](#long-running-processes) ``` ``` - [`yes / nohup / ps / kill`](#yes-nohup-ps-kill) ``` ``` - [`cron / crontab / >>`](#cron) ``` ``` - [Miscellaneous](#advanced-misc) ``` ``` - [`pushd / popd`](#pushd-popd) ``` ``` - [`xdg-open`](#xdg-open) ``` ``` - [`xargs`](#xargs) ``` - [獎勵:有趣但大多無用的東西](#bonus) ``` - [`w / write / wall / lynx`](#w-write-wall-lynx) ``` ``` - [`nautilus / date / cal / bc`](#nautilus-date-cal-bc) ``` --- <a name="the-basics"></a> 基礎 == <a name="first-commands"></a> 第一個指令,瀏覽檔案系統 ------------ 現代檔案系統具有目錄(資料夾)樹,其中目錄要么是*根目錄*(沒有父目錄),要么是*子目錄*(包含在單一其他目錄中,我們稱之為“父目錄”)。向後遍歷檔案樹(從子目錄到父目錄)將始終到達根目錄。有些檔案系統有多個根目錄(如 Windows 的磁碟機: `C:\` 、 `A:\`等),但 Unix 和類別 Unix 系統只有一個名為`\`的根目錄。 <a name="pwd-ls-cd"></a> ### `pwd / ls / cd` [\[ 返回目錄 \]](#toc) 在檔案系統中工作時,使用者始終*在*某個目錄中工作,我們稱之為當前目錄或*工作目錄*。使用`pwd`列印使用者的工作目錄: ``` [ andrew@pc01 ~ ]$ pwd /home/andrew ``` 使用`ls`列出該目錄的內容(檔案和/或子目錄等): ``` [ andrew@pc01 ~ ]$ ls Git TEST jdoc test test.file ``` > **獎金:** > > 使用`ls -a`顯示隱藏(“點”)文件 > > 使用`ls -l`顯示文件詳細訊息 > > 組合多個標誌,如`ls -l -a` > > 有時您可以連結諸如`ls -la`之類的標誌,而不是`ls -l -a` 使用`cd`更改到不同的目錄(更改目錄): ``` [ andrew@pc01 ~ ]$ cd TEST/ [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST [ andrew@pc01 TEST ]$ cd A [ andrew@pc01 A ]$ pwd /home/andrew/TEST/A ``` `cd ..`是「 `cd`到父目錄」的簡寫: ``` [ andrew@pc01 A ]$ cd .. [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST ``` `cd ~`或只是`cd`是「 `cd`到我的主目錄」的簡寫(通常`/home/username`或類似的東西): ``` [ andrew@pc01 TEST ]$ cd [ andrew@pc01 ~ ]$ pwd /home/andrew ``` > **獎金:** > > `cd ~user`表示「 `cd`到`user`的主目錄 > > 您可以使用`cd ../..`等跳轉多個目錄等級。 > > 使用`cd -`返回到最近的目錄 > > `.`是「此目錄」的簡寫,因此`cd .`不會做太多事情 <a name="semicolon-andand-and"></a> ### `; / && / &` [\[ 返回目錄 \]](#toc) 我們在命令列中輸入的內容稱為*命令*,它們總是執行儲存在電腦上某處的一些機器碼。有時這個機器碼是一個內建的Linux命令,有時它是一個應用程式,有時它是你自己寫的一些程式碼。有時,我們會想依序執行一個指令。為此,我們可以使用`;` (分號): ``` [ andrew@pc01 ~ ]$ ls; pwd Git TEST jdoc test test.file /home/andrew ``` 上面的分號表示我首先 ( `ls` ) 列出工作目錄的內容,然後 ( `pwd` ) 列印其位置。連結命令的另一個有用工具是`&&` 。使用`&&`時,如果左側命令失敗,則右側命令將不會執行。 `;`和`&&`都可以在同一行中多次使用: ``` # whoops! I made a typo here! [ andrew@pc01 ~ ]$ cd /Giit/Parser && pwd && ls && cd -bash: cd: /Giit/Parser: No such file or directory # the first command passes now, so the following commands are run [ andrew@pc01 ~ ]$ cd Git/Parser/ && pwd && ls && cd /home/andrew/Git/Parser README.md doc.sh pom.xml resource run.sh shell.sh source src target ``` ....但是與`;` ,即使第一個命令失敗,第二個命令也會執行: ``` # pwd and ls still run, even though the cd command failed [ andrew@pc01 ~ ]$ cd /Giit/Parser ; pwd ; ls -bash: cd: /Giit/Parser: No such file or directory /home/andrew Git TEST jdoc test test.file ``` `&`看起來與`&&`類似,但實際上實現了完全不同的功能。通常,當您執行長時間執行的命令時,命令列將等待該命令完成,然後才允許您輸入另一個命令。在命令後面加上`&`可以防止這種情況發生,並允許您在舊命令仍在執行時執行新命令: ``` [ andrew@pc01 ~ ]$ cd Git/Parser && mvn package & cd [1] 9263 ``` > **額外的好處:**當我們在命令後使用`&`來「隱藏」它時,我們說該作業(或「進程」;這些術語或多或少可以互換)是「後台的」。若要查看目前正在執行的背景作業,請使用`jobs`指令: > ````bash \[ andrew@pc01 ~ \]$ 職位 \[1\]+ 執行 cd Git/Parser/ && mvn package & ``` <a name="getting-help"></a> ## Getting Help <a name="minus-h"></a> ### `-h` [[ Back to Table of Contents ]](#toc) Type `-h` or `--help` after almost any command to bring up a help menu for that command: ``` \[ andrew@pc01 ~ \]$ du --help 用法:你\[選項\]...\[檔案\]... 或: du \[選項\]... --files0-from=F 對目錄遞歸地總結文件集的磁碟使用情況。 長期權的強制性參數對於短期權也是強制性的。 -0, --null 以 NUL 結束每個輸出行,而不是換行符 -a, --all 計算所有檔案的寫入計數,而不僅僅是目錄 ``` --apparent-size print apparent sizes, rather than disk usage; although ``` ``` the apparent size is usually smaller, it may be ``` ``` larger due to holes in ('sparse') files, internal ``` ``` fragmentation, indirect blocks, and the like ``` -B, --block-size=SIZE 在列印前按 SIZE 縮放大小;例如, ``` '-BM' prints sizes in units of 1,048,576 bytes; ``` ``` see SIZE format below ``` … ``` <a name="man"></a> ### `man` [[ Back to Table of Contents ]](#toc) Type `man` before almost any command to bring up a manual for that command (quit `man` with `q`): ``` LS(1) 使用者指令 LS(1) 姓名 ``` ls - list directory contents ``` 概要 ``` ls [OPTION]... [FILE]... ``` 描述 ``` List information about the FILEs (the current directory by default). ``` ``` Sort entries alphabetically if none of -cftuvSUX nor --sort is speci- ``` ``` fied. ``` ``` Mandatory arguments to long options are mandatory for short options ``` ``` too. ``` … ``` <a name="viewing-and-editing-files"></a> ## Viewing and Editing Files <a name="head-tail-cat-less"></a> ### `head / tail / cat / less` [[ Back to Table of Contents ]](#toc) `head` outputs the first few lines of a file. The `-n` flag specifies the number of lines to show (the default is 10): ``` 列印前三行 ===== \[ andrew@pc01 ~ \]$ 頭 -n 3 c 這 文件 有 ``` `tail` outputs the last few lines of a file. You can get the last `n` lines (like above), or you can get the end of the file beginning from the `N`-th line with `tail -n +N`: ``` 從第 4 行開始列印文件末尾 ============== \[ andrew@pc01 ~ \]$ tail -n +4 c 確切地 六 線 ``` `cat` concatenates a list of files and sends them to the standard output stream (usually the terminal). `cat` can be used with just a single file, or multiple files, and is often used to quickly view them. (**Be warned**: if you use `cat` in this way, you may be accused of a [_Useless Use of Cat_ (UUOC)](http://bit.ly/2SPHE4V), but it's not that big of a deal, so don't worry too much about it.) ``` \[ andrew@pc01 ~ \]$ 貓 a 歸檔一個 \[ andrew@pc01 ~ \]$ 貓 ab 歸檔一個 文件b ``` `less` is another tool for quickly viewing a file -- it opens up a `vim`-like read-only window. (Yes, there is a command called `more`, but `less` -- unintuitively -- offers a superset of the functionality of `more` and is recommended over it.) Learn more (or less?) about [less](http://man7.org/linux/man-pages/man1/less.1.html) and [more](http://man7.org/linux/man-pages/man1/more.1.html) at their `man` pages. <a name="nano-nedit"></a> ### `nano / nedit` [[ Back to Table of Contents ]](#toc) `nano` is a minimalistic command-line text editor. It's a great editor for beginners or people who don't want to learn a million shortcuts. It was more than sufficient for me for the first few years of my coding career (I'm only now starting to look into more powerful editors, mainly because defining your own syntax highlighting in `nano` can be a bit of a pain.) `nedit` is a small graphical editor, it opens up an X Window and allows point-and-click editing, drag-and-drop, syntax highlighting and more. I use `nedit` sometimes when I want to make small changes to a script and re-run it over and over. Other common CLI (command-line interface) / GUI (graphical user interface) editors include `emacs`, `vi`, `vim`, `gedit`, Notepad++, Atom, and lots more. Some cool ones that I've played around with (and can endorse) include Micro, Light Table, and VS Code. All modern editors offer basic conveniences like search and replace, syntax highlighting, and so on. `vi(m)` and `emacs` have more features than `nano` and `nedit`, but they have a much steeper learning curve. Try a few different editors out and find one that works for you! <a name="creating-and-deleting-files"></a> ## Creating and Deleting Files and Directories <a name="touch"></a> ### `touch` [[ Back to Table of Contents ]](#toc) `touch` was created to modify file timestamps, but it can also be used to quickly create an empty file. You can create a new file by opening it with a text editor, like `nano`: ``` \[ andrew@pc01 前 \]$ ls \[ andrew@pc01 ex \]$ 奈米 a ``` _...editing file..._ ``` \[ andrew@pc01 前 \]$ ls A ``` ...or by simply using `touch`: ``` \[ andrew@pc01 ex \]$ touch b && ls ab ``` > **Bonus**: > > Background a process with \^z (Ctrl+z) > > ```bash > [ andrew@pc01 ex ]$ nano a > ``` > > _...editing file, then hit \^z..._ > > ```bash > Use fg to return to nano > > [1]+ Stopped nano a > [ andrew@pc01 ex ]$ fg > ``` > > _...editing file again..._ --- > **Double Bonus:** > > Kill the current (foreground) process by pressing \^c (Ctrl+c) while it’s running > > Kill a background process with `kill %N` where `N` is the job index shown by the `jobs` command <a name="mkdir-rm-rmdir"></a> ### `mkdir / rm / rmdir` [[ Back to Table of Contents ]](#toc) `mkdir` is used to create new, empty directories: ``` \[ andrew@pc01 ex \]$ ls && mkdir c && ls ab ABC ``` You can remove any file with `rm` -- but be careful, this is non-recoverable! ``` \[ andrew@pc01 ex \]$ rm a && ls 西元前 ``` You can add an _"are you sure?"_ prompt with the `-i` flag: ``` \[ andrew@pc01 前 \]$ rm -ib rm:刪除常規空文件“b”? y ``` Remove an empty directory with `rmdir`. If you `ls -a` in an empty directory, you should only see a reference to the directory itself (`.`) and a reference to its parent directory (`..`): ``` \[ andrew@pc01 ex \]$ rmdir c && ls -a 。 .. ``` `rmdir` removes empty directories only: ``` \[ andrew@pc01 ex \]$ cd .. && ls 測試/ \*.txt 0.txt 1.txt a a.txt bc \[ andrew@pc01 ~ \]$ rmdir 測試/ rmdir:無法刪除“test/”:目錄不為空 ``` ...but you can remove a directory -- and all of its contents -- with `rm -rf` (`-r` = recursive, `-f` = force): ``` \[ andrew@pc01 ~ \]$ rm –rf 測試 ``` <a name="moving-and-copying-files"></a> ## Moving and Copying Files, Making Links, Command History <a name="mv-cp-ln"></a> ### `mv / cp / ln` [[ Back to Table of Contents ]](#toc) `mv` moves / renames a file. You can `mv` a file to a new directory and keep the same file name or `mv` a file to a "new file" (rename it): ``` \[ andrew@pc01 ex \]$ ls && mv ae && ls A B C D BCDE ``` `cp` copies a file: ``` \[ andrew@pc01 ex \]$ cp e e2 && ls BCDE E2 ``` `ln` creates a hard link to a file: ``` ln 的第一個參數是 TARGET,第二個參數是 NEW LINK ================================= \[ andrew@pc01 ex \]$ ln bf && ls bcde e2 f ``` `ln -s` creates a soft link to a file: ``` \[ andrew@pc01 ex \]$ ln -sbg && ls BCDE E2 FG ``` Hard links reference the same actual bytes in memory which contain a file, while soft links refer to the original file name, which itself points to those bytes. [You can read more about soft vs. hard links here.](http://bit.ly/2D0W8cN) <a name="command-history"></a> ### Command History [[ Back to Table of Contents ]](#toc) `bash` has two big features to help you complete and re-run commands, the first is _tab completion_. Simply type the first part of a command, hit the \<tab\> key, and let the terminal guess what you're trying to do: ``` \[ andrew@pc01 目錄 \]$ ls 另一個長檔名 這是一個長檔名 一個新檔名 \[ andrew@pc01 目錄 \]$ ls t ``` _...hit the TAB key after typing `ls t` and the command is completed..._ ``` \[ andrew@pc01 dir \]$ ls 這是檔名 這是長檔名 ``` You may have to hit \<TAB\> multiple times if there's an ambiguity: ``` \[ andrew@pc01 目錄 \]$ ls a \[ andrew@pc01 目錄 \]$ ls an 一個新檔名另一個長檔名 ``` `bash` keeps a short history of the commands you've typed previously and lets you search through those commands by typing \^r (Ctrl+r): ``` \[ andrew@pc01 目錄 \] ``` _...hit \^r (Ctrl+r) to search the command history..._ ``` (反向搜尋)``: ``` _...type 'anew' and the last command containing this is found..._ ``` (reverse-i-search)`anew': 觸碰新檔名 ``` <a name="directory-trees-disk-usage-processes"></a> ## Directory Trees, Disk Usage, and Processes <a name="mkdir--p-tree"></a> ### `mkdir –p / tree` [[ Back to Table of Contents ]](#toc) `mkdir`, by default, only makes a single directory. This means that if, for instance, directory `d/e` doesn't exist, then `d/e/f` can't be made with `mkdir` by itself: ``` \[ andrew@pc01 ex \]$ ls && mkdir d/e/f ABC mkdir:無法建立目錄「d/e/f」:沒有這樣的檔案或目錄 ``` But if we pass the `-p` flag to `mkdir`, it will make all directories in the path if they don't already exist: ``` \[ andrew@pc01 ex \]$ mkdir -pd/e/f && ls A B C D ``` `tree` can help you better visualise a directory's structure by printing a nicely-formatted directory tree. By default, it prints the entire tree structure (beginning with the specified directory), but you can restrict it to a certain number of levels with the `-L` flag: ``` \[ andrew@pc01 前 \]$ 樹 -L 2 。 |-- 一個 |-- b |-- c `--d ``` `--e ``` 3個目錄,2個文件 ``` You can hide empty directories in `tree`'s output with `--prune`. Note that this also removes "recursively empty" directories, or directories which aren't empty _per se_, but which contain only other empty directories, or other recursively empty directories: ``` \[ andrew@pc01 ex \]$ 樹 --prune 。 |-- 一個 `--b ``` <a name="df-du-ps"></a> ### `df / du / ps` [[ Back to Table of Contents ]](#toc) `df` is used to show how much space is taken up by files for the disks or your system (hard drives, etc.). ``` \[ andrew@pc01 前 \]$ df -h 已使用的檔案系統大小 可用 使用% 安裝於 udev 126G 0 126G 0% /dev tmpfs 26G 2.0G 24G 8% /執行 /dev/mapper/ubuntu--vg-root 1.6T 1.3T 252G 84% / … ``` In the above command, `-h` doesn't mean "help", but "human-readable". Some commands use this convention to display file / disk sizes with `K` for kilobytes, `G` for gigabytes, and so on, instead of writing out a gigantic integer number of bytes. `du` shows file space usage for a particular directory and its subdirectories. If you want to know how much space is free on a given hard drive, use `df`; if you want to know how much space a directory is taking up, use `du`: ``` \[ andrew@pc01 ex \]$ 你 4 ./d/e/f 8./d/e 12 ./天 4./c 20 . ``` `du` takes a `--max-depth=N` flag, which only shows directories `N` levels down (or fewer) from the specified directory: ``` \[ andrew@pc01 ex \]$ du -h --max-深度=1 12K./天 4.0K./c 20K。 ``` `ps` shows all of the user's currently-running processes (aka. jobs): ``` \[ andrew@pc01 前 \]$ ps PID TTY 時間 CMD 16642 分/15 00:00:00 ps 25409 點/15 00:00:00 重擊 ``` <a name="basic-misc"></a> ## Miscellaneous <a name="passwd-logout-exit"></a> ### `passwd / logout / exit` [[ Back to Table of Contents ]](#toc) Change your account password with `passwd`. It will ask for your current password for verification, then ask you to enter the new password twice, so you don't make any typos: ``` \[ andrew@pc01 目錄 \]$ 密碼 更改安德魯的密碼。 (目前)UNIX 密碼: 輸入新的 UNIX 密碼: 重新輸入新的 UNIX 密碼: passwd:密碼更新成功 ``` `logout` exits a shell you’ve logged in to (where you have a user account): ``` \[ andrew@pc01 目錄 \]$ 註銷 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` `exit` exits any kind of shell: ``` \[ andrew@pc01 ~ \]$ 退出 登出 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` <a name="clear-glob"></a> ### `clear / *` [[ Back to Table of Contents ]](#toc) Run `clear` to move the current terminal line to the top of the screen. This command just adds blank lines below the current prompt line. It's good for clearing your workspace. Use the glob (`*`, aka. Kleene Star, aka. wildcard) when looking for files. Notice the difference between the following two commands: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/ PArrayUtils.java PFile.java PSQLFile.java PWatchman.java PDateTimeUtils.java PFixedWidthFile.java PStringUtils.java PXSVFile.java PDelimitedFile.java PNode.java PTextFile.java Parser.java \[ andrew@pc01 ~ \]$ ls Git/Parser/source/PD\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` The glob can be used multiple times in a command and matches zero or more characers: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/P *D* m\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` <a name="intermediate"></a> # Intermediate <a name="disk-memory-processor"></a> ## Disk, Memory, and Processor Usage <a name="ncdu"></a> ### `ncdu` [[ Back to Table of Contents ]](#toc) `ncdu` (NCurses Disk Usage) provides a navigable overview of file space usage, like an improved `du`. It opens a read-only `vim`-like window (press `q` to quit): ``` \[ andrew@pc01 ~ \]$ ncdu ncdu 1.11 ~ 使用箭頭鍵導航,按 ?求助 \--- /home/安德魯 ------------------------------------------- ------------------ 148.2 MiB \[##########\] /.m2 91.5 MiB \[######\] /.sbt 79.8 MiB \[######\] /.cache 64.9 MiB \[####\] /.ivy2 40.6 MiB \[##\] /.sdkman 30.2 MiB \[##\] /.local 27.4 MiB \[#\] /.mozilla 24.4 MiB \[#\] /.nanobackups 10.2 MiB \[ \] .confout3.txt ``` 8.4 MiB [ ] /.config ``` ``` 5.9 MiB [ ] /.nbi ``` ``` 5.8 MiB [ ] /.oh-my-zsh ``` ``` 4.3 MiB [ ] /Git ``` ``` 3.7 MiB [ ] /.myshell ``` ``` 1.7 MiB [ ] /jdoc ``` ``` 1.5 MiB [ ] .confout2.txt ``` ``` 1.5 MiB [ ] /.netbeans ``` ``` 1.1 MiB [ ] /.jenv ``` 564.0 KiB \[ \] /.rstudio-desktop 磁碟使用總量:552.7 MiB 表觀大小:523.6 MiB 專案:14618 ``` <a name="top-htop"></a> ### `top / htop` [[ Back to Table of Contents ]](#toc) `top` displays all currently-running processes and their owners, memory usage, and more. `htop` is an improved, interactive `top`. (Note: you can pass the `-u username` flag to restrict the displayed processes to only those owner by `username`.) ``` \[ andrew@pc01 ~ \]$ htop 1 \[ 0.0%\] 9 \[ 0.0%\] 17 \[ 0.0%\] 25 \[ 0.0%\] 2 \[ 0.0%\] 10 \[ 0.0%\] 18 \[ 0.0%\] 26 \[ 0.0%\] 3 \[ 0.0%\] 11 \[ 0.0%\] 19 \[ 0.0%\] 27 \[ 0.0%\] 4 \[ 0.0%\] 12 \[ 0.0%\] 20 \[ 0.0%\] 28 \[ 0.0%\] 5 \[ 0.0%\] 13 \[ 0.0%\] 21 \[| 1.3%\] 29 \[ 0.0%\] 6 \[ 0.0%\] 14 \[ 0.0%\] 22 \[ 0.0%\] 30 \[| 0.6%\] 7 \[ 0.0%\] 15 \[ 0.0%\] 23 \[ 0.0%\] 31 \[ 0.0%\] 8 \[ 0.0%\] 16 \[ 0.0%\] 24 \[ 0.0%\] 32 \[ 0.0%\] Mem\[|||||||||||||||||||1.42G/252G\] 任務:188、366 個; 1 執行 交換電壓\[| 2.47G/256G\]平均負載:0.00 0.00 0.00 ``` Uptime: 432 days(!), 00:03:55 ``` PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ 指令 9389 安德魯 20 0 23344 3848 2848 R 1.3 0.0 0:00.10 htop 10103 根 20 0 3216M 17896 2444 S 0.7 0.0 5h48:56 /usr/bin/dockerd ``` 1 root 20 0 181M 4604 2972 S 0.0 0.0 15:29.66 /lib/systemd/syst ``` 533 根 20 0 44676 6908 6716 S 0.0 0.0 11:19.77 /lib/systemd/syst 546 根 20 0 244M 0 0 S 0.0 0.0 0:01.39 /sbin/lvmetad -f 1526 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.00 /usr/sbin/ModemMa 1544 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.06 /usr/sbin/ModemMa F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit ``` <a name="REPLs-software-versions"></a> ## REPLs and Software Versions <a name="REPLs"></a> ### REPLs [[ Back to Table of Contents ]](#toc) A **REPL** is a Read-Evaluate-Print Loop, similar to the command line, but usually used for particular programming languages. You can open the Python REPL with the `python` command (and quit with the `quit()` function): ``` \[ andrew@pc01 ~ \]$ python Python 3.5.2(默認,2018 年 11 月 12 日,13:43:14)... > > > 辭職() ``` Open the R REPL with the `R` command (and quit with the `q()` function): ``` \[ andrew@pc01 ~ \]$ R R版3.5.2(2018-12-20)--「蛋殼冰屋」... > q() 儲存工作區影像? \[是/否/c\]: 否 ``` Open the Scala REPL with the `scala` command (and quit with the `:quit` command): ``` \[ andrew@pc01 ~ \]$ scala 歡迎使用 Scala 2.11.12 ... 斯卡拉>:退出 ``` Open the Java REPL with the `jshell` command (and quit with the `/exit` command): ``` \[ andrew@pc01 ~ \]$ jshell |歡迎使用 JShell——版本 11.0.1 ... jshell> /退出 ``` Alternatively, you can exit any of these REPLs with \^d (Ctrl+d). \^d is the EOF (end of file) marker on Unix and signifies the end of input. <a name="version"></a> ### `-version / --version / -v` [[ Back to Table of Contents ]](#toc) Most commands and programs have a `-version` or `--version` flag which gives the software version of that command or program. Most applications make this information easily available: ``` \[ andrew@pc01 ~ \]$ ls --version ls (GNU coreutils) 8.25 ... \[ andrew@pc01 ~ \]$ ncdu -版本 NCDU 1.11 \[ andrew@pc01 ~ \]$ python --version Python 3.5.2 ``` ...but some are less intuitive: ``` \[ andrew@pc01 ~ \]$ sbt scalaVersion … \[資訊\]2.12.4 ``` Note that some programs use `-v` as a version flag, while others use `-v` to mean "verbose", which will run the application while printing lots of diagnostic or debugging information: ``` SCP(1) BSD 通用指令手冊 SCP(1) 姓名 ``` scp -- secure copy (remote file copy program) ``` … -v 詳細模式。導致 scp 和 ssh(1) 列印偵錯訊息 ``` about their progress. This is helpful in debugging connection, ``` ``` authentication, and configuration problems. ``` … ``` <a name="env-vars-aliases"></a> ## Environment Variables and Aliases <a name="env-vars"></a> ### Environment Variables [[ Back to Table of Contents ]](#toc) **Environment variables** (sometimes shortened to "env vars") are persistent variables that can be created and used within your `bash` shell. They are defined with an equals sign (`=`) and used with a dollar sign (`$`). You can see all currently-defined env vars with `printenv`: ``` \[ andrew@pc01 ~ \]$ printenv SPARK\_HOME=/usr/local/spark 術語=xterm … ``` Set a new environment variable with an `=` sign (don't put any spaces before or after the `=`, though!): ``` \[ andrew@pc01 ~ \]$ myvar=你好 ``` Print a specific env var to the terminal with `echo` and a preceding `$` sign: ``` \[ andrew@pc01 ~ \]$ echo $myvar 你好 ``` Environment variables which contain spaces or other whitespace should be surrounded by quotes (`"..."`). Note that reassigning a value to an env var overwrites it without warning: ``` \[ andrew@pc01 ~ \]$ myvar="你好,世界!" && 回顯 $myvar 你好世界! ``` Env vars can also be defined using the `export` command. When defined this way, they will also be available to sub-processes (commands called from this shell): ``` \[ andrew@pc01 ~ \]$ export myvar="另一" && echo $myvar 另一個 ``` You can unset an environment variable by leaving the right-hand side of the `=` blank or by using the `unset` command: ``` \[ andrew@pc01 ~ \]$ 取消設定 mynewvar \[ andrew@pc01 ~ \]$ echo $mynewvar ``` <a name="aliases"></a> ### Aliases [[ Back to Table of Contents ]](#toc) **Aliases** are similar to environment variables but are usually used in a different way -- to replace long commands with shorter ones: ``` \[ andrew@pc01 apidocs \]$ ls -l -a -h -t 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … \[ andrew@pc01 apidocs \]$ 別名 lc="ls -l -a -h -t" \[ andrew@pc01 apidocs \]$ lc 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … ``` You can remove an alias with `unalias`: ``` \[ andrew@pc01 apidocs \]$ unalias lc \[ andrew@pc01 apidocs \]$ lc 目前未安裝程式“lc”。 … ``` > **Bonus:** > > [Read about the subtle differences between environment variables and aliases here.](http://bit.ly/2TDG8Tx) > > [Some programs, like **git**, allow you to define aliases specifically for that software.](http://bit.ly/2TG8X1A) <a name="basic-bash-scripting"></a> ## Basic `bash` Scripting <a name="bash-scripts"></a> ### `bash` Scripts [[ Back to Table of Contents ]](#toc) `bash` scripts (usually ending in `.sh`) allow you to automate complicated processes, packaging them into reusable functions. A `bash` script can contain any number of normal shell commands: ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file && ls" > ex.sh ``` A shell script can be executed with the `source` command or the `sh` command: ``` \[ andrew@pc01 ~ \]$ 源 ex.sh 桌面 Git TEST c ex.sh 專案測試 桌面 Git TEST c ex.sh 檔案專案測試 ``` Shell scripts can be made executable with the `chmod` command (more on this later): ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file2 && ls" > ex2.sh \[ andrew@pc01 ~ \]$ chmod +x ex2.sh ``` An executable shell script can be run by preceding it with `./`: ``` \[ andrew@pc01 ~ \]$ ./ex2.sh 桌面 Git TEST c ex.sh ex2.sh 檔案專案測試 桌面 Git TEST c ex.sh ex2.sh 檔案 file2 專案測試 ``` Long lines of code can be split by ending a command with `\`: ``` \[ andrew@pc01 ~ \]$ echo "for i in {1..3}; do echo \\ > \\"歡迎\\$i次\\";完成” > ex3.sh ``` Bash scripts can contain loops, functions, and more! ``` \[ andrew@pc01 ~ \]$ 源 ex3.sh 歡迎1次 歡迎2次 歡迎3次 ``` <a name="custom-prompt-ls"></a> ### Custom Prompt and `ls` [[ Back to Table of Contents ]](#toc) Bash scripting can make your life a whole lot easier and more colourful. [Check out this great bash scripting cheat sheet.](https://devhints.io/bash) `$PS1` (Prompt String 1) is the environment variable that defines your main shell prompt ([learn about the other prompts here](http://bit.ly/2SPgsmT)): ``` \[ andrew@pc01 ~ \]$ printf "%q" $PS1 $'\\n\\\[\\E\[1m\\\]\\\[\\E\[30m\\\]\\A'$'\\\[\\E\[37m\\\]|\\\[\\E\[36m\\\]\\u\\\[\\E\[37m \\\]@\\\[\\E\[34m\\\]\\h'$'\\\[\\E\[32m\\\]\\W\\\[\\E\[37m\\\]|'$'\\\[\\E(B\\E\[m\\\] ' ``` You can change your default prompt with the `export` command: ``` \[ andrew@pc01 ~ \]$ export PS1="\\n此處指令> " 此處指令> echo $PS1 \\n此處指令> ``` ...[you can add colours, too!](http://bit.ly/2TMbEit): ``` 此處指令> export PS1="\\e\[1;31m\\n程式碼: \\e\[39m" (這應該是紅色的,但在 Markdown 中可能不會這樣顯示) =============================== 程式碼:回顯$PS1 \\e\[1;31m\\n程式碼: \\e\[39m ``` You can also change the colours shown by `ls` by editing the `$LS_COLORS` environment variable: ``` (同樣,這些顏色可能不會出現在 Markdown 中) =========================== 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 程式碼:匯出 LS\_COLORS='di=31:fi=0:ln=96:or=31:mi=31:ex=92' 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="config-files"></a> ## Config Files <a name="config-bashrc"></a> ### Config Files / `.bashrc` [[ Back to Table of Contents ]](#toc) If you tried the commands in the last section and logged out and back in, you may have noticed that your changes disappeared. _config_ (configuration) files let you maintain settings for your shell or for a particular program every time you log in (or run that program). The main configuration file for a `bash` shell is the `~/.bashrc` file. Aliases, environment variables, and functions added to `~/.bashrc` will be available every time you log in. Commands in `~/.bashrc` will be run every time you log in. If you edit your `~/.bashrc` file, you can reload it without logging out by using the `source` command: ``` \[ andrew@pc01 ~ \]$ nano ~/.bashrc ``` _...add the line `echo “~/.bashrc loaded!”` to the top of the file_... ``` \[ andrew@pc01 ~ \]$ 源 ~/.bashrc ~/.bashrc 已載入! ``` _...log out and log back in..._ ``` 最後登入:2019 年 1 月 11 日星期五 10:29:07 從 111.11.11.111 ~/.bashrc 已加載! \[ 安德魯@pc01 ~ \] ``` <a name="types-of-shells"></a> ### Types of Shells [[ Back to Table of Contents ]](#toc) _Login_ shells are shells you log in to (where you have a username). _Interactive_ shells are shells which accept commands. Shells can be login and interactive, non-login and non-interactive, or any other combination. In addition to `~/.bashrc`, there are a few other scripts which are `sourced` by the shell automatically when you log in or log out. These are: - `/etc/profile` - `~/.bash_profile` - `~/.bash_login` - `~/.profile` - `~/.bash_logout` - `/etc/bash.bash_logout` Which of these scripts are sourced, and the order in which they're sourced, depend on the type of shell opened. See [the bash man page](https://linux.die.net/man/1/bash) and [these](http://bit.ly/2TGCwA8) Stack Overflow [posts](http://bit.ly/2TFHFsf) for more information. Note that `bash` scripts can `source` other scripts. For instance, in your `~/.bashrc`, you could include the line: ``` 來源~/.bashrc\_addl ``` ...which would also `source` that `.bashrc_addl` script. This file can contain its own aliases, functions, environment variables, and so on. It could, in turn, `source` other scripts, as well. (Be careful to avoid infinite loops of script-sourcing!) It may be helpful to split commands into different shell scripts based on functionality or machine type (Ubuntu vs. Red Hat vs. macOS), for example: - `~/.bash_ubuntu` -- configuration specific to Ubuntu-based machines - `~/.bashrc_styles` -- aesthetic settings, like `PS1` and `LS_COLORS` - `~/.bash_java` -- configuration specific to the Java language I try to keep separate `bash` files for aesthetic configurations and OS- or machine-specific code, and then I have one big `bash` file containing shortcuts, etc. that I use on every machine and every OS. Note that there are also _different shells_. `bash` is just one kind of shell (the "Bourne Again Shell"). Other common ones include `zsh`, `csh`, `fish`, and more. Play around with different shells and find one that's right for you, but be aware that this tutorial contains `bash` shell commands only and not everything listed here (maybe none of it) will be applicable to shells other than `bash`. <a name="finding-things"></a> ## Finding Things <a name="whereis-which-whatis"></a> ### `whereis / which / whatis` [[ Back to Table of Contents ]](#toc) `whereis` searches for "possibly useful" files related to a particular command. It will attempt to return the location of the binary (executable machine code), source (code source files), and `man` page for that command: ``` \[ andrew@pc01 ~ \]$ whereis ls ls: /bin/ls /usr/share/man/man1/ls.1.gz ``` `which` will only return the location of the binary (the command itself): ``` \[ andrew@pc01 ~ \]$ 其中 ls /bin/ls ``` `whatis` prints out the one-line description of a command from its `man` page: ``` \[ andrew@pc01 ~ \]$ 什麼是哪裡是哪個什麼是 whereis (1) - 尋找指令的二進位、原始檔和手冊頁文件 which (1) - 定位指令 Whatis (1) - 顯示一行手冊頁描述 ``` `which` is useful for finding the "original version" of a command which may be hidden by an alias: ``` \[ andrew@pc01 ~ \]$ 別名 ls="ls -l" “original” ls 已被上面定義的別名“隱藏” =========================== \[ andrew@pc01 ~ \]$ ls 總計 36 drwxr-xr-x 2 安德魯 andrew 4096 Jan 9 14:47 桌面 drwxr-xr-x 4 安德魯 安德魯 4096 十二月 6 10:43 Git … 但我們仍然可以使用返回的位置來呼叫「原始」ls ======================= \[ andrew@pc01 ~ \]$ /bin/ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="locate-find"></a> ### `locate / find` [[ Back to Table of Contents ]](#toc) `locate` finds a file anywhere on the system by referring to a semi-regularly-updated cached list of files: ``` \[ andrew@pc01 ~ \]$ 找到 README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.jenv/README.md /home/andrew/.myshell/README.md … ``` Because it's just searching a list, `locate` is usually faster than the alternative, `find`. `find` iterates through the file system to find the file you're looking for. Because it's actually looking at the files which _currently_ exist on the system, though, it will always return an up-to-date list of files, which is not necessarily true with `locate`. ``` \[ andrew@pc01 ~ \]$ find ~/ -iname "README.md" /home/andrew/.jenv/README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.oh-my-zsh/plugins/ant/README.md … ``` `find` was written for the very first version of Unix in 1971, and is therefore much more widely available than `locate`, which was added to GNU in 1994. `find` has many more features than `locate`, and can search by file age, size, ownership, type, timestamp, permissions, depth within the file system; `find` can search using regular expressions, execute commands on files it finds, and more. When you need a fast (but possibly outdated) list of files, or you’re not sure what directory a particular file is in, use `locate`. When you need an accurate file list, maybe based on something other than the files’ names, and you need to do something with those files, use `find`. <a name="downloading-things"></a> ## Downloading Things <a name="ping-wget-curl"></a> ### `ping / wget / curl` [[ Back to Table of Contents ]](#toc) `ping` attempts to open a line of communication with a network host. Mainly, it's used to check whether or not your Internet connection is down: ``` \[ andrew@pc01 ~ \]$ ping google.com PING google.com (74.125.193.100) 56(84) 位元組資料。 使用 32 位元組資料 Ping 74.125.193.100: 來自 74.125.193.100 的回覆:位元組=32 時間<1ms TTL=64 … ``` `wget` is used to easily download a file from the Internet: ``` \[ andrew@pc01 ~ \]$ wget \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso ``` `curl` can be used just like `wget` (don’t forget the `--output` flag): ``` \[ andrew@pc01 ~ \]$ 捲曲 \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso \\ > \--輸出ubuntu.iso ``` `curl` and `wget` have their own strengths and weaknesses. `curl` supports many more protocols and is more widely available than `wget`; `curl` can also send data, while `wget` can only receive data. `wget` can download files recursively, while `curl` cannot. In general, I use `wget` when I need to download things from the Internet. I don’t often need to send data using `curl`, but it’s good to be aware of it for the rare occasion that you do. <a name="apt-gunzip-tar-gzip"></a> ### `apt / gunzip / tar / gzip` [[ Back to Table of Contents ]](#toc) Debian-descended Linux distributions have a fantastic package management tool called `apt`. It can be used to install, upgrade, or delete software on your machine. To search `apt` for a particular piece of software, use `apt search`, and install it with `apt install`: ``` \[ andrew@pc01 ~ \]$ apt 搜尋漂白位 ...bleachbit/bionic、bionic 2.0-2 全部 從系統中刪除不需要的文件 您需要“sudo”來安裝軟體 ============== \[ andrew@pc01 ~ \]$ sudo apt installbleachbit ``` Linux software often comes packaged in `.tar.gz` ("tarball") files: ``` \[ andrew@pc01 ~ \]$ wget \\ > https://github.com/atom/atom/releases/download/v1.35.0-beta0/atom-amd64.tar.gz ``` ...these types of files can be unzipped with `gunzip`: ``` \[ andrew@pc01 ~ \]$gunzipatom-amd64.tar.gz && ls 原子 amd64.tar ``` A `.tar.gz` file will be `gunzip`-ped to a `.tar` file, which can be extracted to a directory of files using `tar -xf` (`-x` for "extract", `-f` to specify the file to "untar"): ``` \[ andrew@pc01 ~ \]$ tar -xfatom-amd64.tar && mv \\ 原子-beta-1.35.0-beta0-amd64 原子 && ls 原子atom-amd64.tar ``` To go in the reverse direction, you can create (`-c`) a tar file from a directory and zip it (or unzip it, as appropriate) with `-z`: ``` \[ andrew@pc01 ~ \]$ tar -zcf 壓縮.tar.gz 原子 && ls 原子atom-amd64.tar壓縮.tar.gz ``` `.tar` files can also be zipped with `gzip`: ``` \[ andrew@pc01 ~ \]$ gzipatom-amd64.tar && ls 原子 原子-amd64.tar.gz 壓縮.tar.gz ``` <a name="redirecting-io"></a> ## Redirecting Input and Output <a name="pipe-gt-lt-echo-printf"></a> ### `| / > / < / echo / printf` [[ Back to Table of Contents ]](#toc) By default, shell commands read their input from the standard input stream (aka. stdin or 0) and write to the standard output stream (aka. stdout or 1), unless there’s an error, which is written to the standard error stream (aka. stderr or 2). `echo` writes text to stdout by default, which in most cases will simply print it to the terminal: ``` \[ andrew@pc01 ~ \]$ 回顯“你好” 你好 ``` The pipe operator, `|`, redirects the output of the first command to the input of the second command: ``` 'wc'(字數)傳回檔案中的行數、字數、位元組數 ======================== \[ andrew@pc01 ~ \]$ echo "範例文件" |廁所 ``` 1 2 17 ``` ``` `>` redirects output from stdout to a particular location ``` \[ andrew@pc01 ~ \]$ echo "test" > 文件 && 頭文件 測試 ``` `printf` is an improved `echo`, allowing formatting and escape sequences: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" 1 3 2 ``` `<` gets input from a particular location, rather than stdin: ``` 'sort' 依字母/數字順序對檔案的行進行排序 ======================== \[ andrew@pc01 ~ \]$ sort <(printf "1\\n3\\n2") 1 2 3 ``` Rather than a [UUOC](#viewing-and-editing-files), the recommended way to send the contents of a file to a command is to use `<`. Note that this causes data to "flow" right-to-left on the command line, rather than (the perhaps more natural, for English-speakers) left-to-right: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" > 文件 && 排序 < 文件 1 2 3 ``` <a name="std-tee"></a> ### `0 / 1 / 2 / tee` [[ Back to Table of Contents ]](#toc) 0, 1, and 2 are the standard input, output, and error streams, respectively. Input and output streams can be redirected with the `|`, `>`, and `<` operators mentioned previously, but stdin, stdout, and stderr can also be manipulated directly using their numeric identifiers: Write to stdout or stderr with `>&1` or `>&2`: ``` \[ andrew@pc01 ~ \]$ 貓測試 回顯“標準輸出”>&1 回顯“標準錯誤”>&2 ``` By default, stdout and stderr both print output to the terminal: ``` \[ andrew@pc01 ~ \]$ ./測試 標準錯誤 標準輸出 ``` Redirect stdout to `/dev/null` (only print output sent to stderr): ``` \[ andrew@pc01 ~ \]$ ./test 1>/dev/null 標準錯誤 ``` Redirect stderr to `/dev/null` (only print output sent to stdout): ``` \[ andrew@pc01 ~ \]$ ./test 2>/dev/null 標準輸出 ``` Redirect all output to `/dev/null` (print nothing): ``` \[ andrew@pc01 ~ \]$ ./test &>/dev/null ``` Send output to stdout and any number of additional locations with `tee`: ``` \[ andrew@pc01 ~ \]$ ls && echo "測試" | tee 文件1 文件2 文件3 && ls 文件0 測試 文件0 文件1 文件2 文件3 ``` <a name="advanced"></a> # Advanced <a name="superuser"></a> ## Superuser <a name="sudo-su"></a> ### `sudo / su` [[ Back to Table of Contents ]](#toc) You can check what your username is with `whoami`: ``` \[ andrew@pc01 abc \]$ whoami 安德魯 ``` ...and run a command as another user with `sudo -u username` (you will need that user's password): ``` \[ andrew@pc01 abc \]$ sudo -u 測試觸摸 def && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def ``` If `–u` is not provided, the default user is the superuser (usually called "root"), with unlimited permissions: ``` \[ andrew@pc01 abc \]$ sudo touch ghi && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def -rw-r--r-- 1 root root 0 Jan 11 20:14 ghi ``` Use `su` to become another user temporarily (and `exit` to switch back): ``` \[ andrew@pc01 abc \]$ su 測試 密碼: test@pc01:/home/andrew/abc$ whoami 測試 test@pc01:/home/andrew/abc$ 退出 出口 \[ andrew@pc01 abc \]$ whoami 安德魯 ``` [Learn more about the differences between `sudo` and `su` here.](http://bit.ly/2SKQH77) <a name="click-click"></a> ### `!!` [[ Back to Table of Contents ]](#toc) The superuser (usually "root") is the only person who can install software, create users, and so on. Sometimes it's easy to forget that, and you may get an error: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? ``` You could retype the command and add `sudo` at the front of it (run it as the superuser): ``` \[ andrew@pc01 ~ \]$ sudo apt install ruby 正在閱讀包裝清單... ``` Or, you could use the `!!` shortcut, which retains the previous command: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? \[ andrew@pc01 ~ \]$ sudo !! sudo apt 安裝 ruby 正在閱讀包裝清單... ``` By default, running a command with `sudo` (and correctly entering the password) allows the user to run superuser commands for the next 15 minutes. Once those 15 minutes are up, the user will again be prompted to enter the superuser password if they try to run a restricted command. <a name="file-permissions"></a> ## File Permissions <a name="file-permissions-sub"></a> ### File Permissions [[ Back to Table of Contents ]](#toc) Files may be able to be read (`r`), written to (`w`), and/or executed (`x`) by different users or groups of users, or not at all. File permissions can be seen with the `ls -l` command and are represented by 10 characters: ``` \[ andrew@pc01 ~ \]$ ls -lh 總計 8 drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxr-xr-x 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rw-r--r-- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The first character of each line represents the type of file, (`d` = directory, `l` = link, `-` = regular file, and so on); then there are three groups of three characters which represent the permissions held by the user (u) who owns the file, the permissions held by the group (g) which owns the file, and the permissions held any other (o) users. (The number which follows this string of characters is the number of links in the file system to that file (4 or 1 above).) `r` means that person / those people have read permission, `w` is write permission, `x` is execute permission. If a directory is “executable”, that means it can be opened and its contents can be listed. These three permissions are often represented with a single three-digit number, where, if `x` is enabled, the number is incremented by 1, if `w` is enabled, the number is incremented by 2, and if `r` is enabled, the number is incremented by 4. Note that these are equivalent to binary digits (`r-x` -> `101` -> `5`, for example). So the above three files have permissions of 755, 755, and 644, respectively. The next two strings in each list are the name of the owner (`andrew`, in this case) and the group of the owner (also `andrew`, in this case). Then comes the size of the file, its most recent modification time, and its name. The `–h` flag makes the output human readable (i.e. printing `4.0K` instead of `4096` bytes). <a name="chmod-chown"></a> ### `chmod / chown` [[ Back to Table of Contents ]](#toc) File permissions can be modified with `chmod` by setting the access bits: ``` \[ andrew@pc01 ~ \]$ chmod 777 測試 && chmod 000 tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 \---------- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` ...or by adding (`+`) or removing (`-`) `r`, `w`, and `x` permissions with flags: ``` \[ andrew@pc01 ~ \]$ chmod +rwx Tist && chmod -w 測試 && ls -lh chmod:測試:新權限是 r-xrwxrwx,而不是 r-xr-xr-x 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -r-xrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rwxr-xr-x 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The user who owns a file can be changed with `chown`: ``` \[ andrew@pc01 ~ \]$ sudo chown 碼頭測試 ``` The group which owns a file can be changed with `chgrp`: ``` \[ andrew@pc01 ~ \]$ sudo chgrp hadoop tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 \-----w--w- 1 瑪麗娜·安德魯 2011 年 1 月 40 日 16:16 測試 -rwxr-xr-x 1 安德魯 hadoop 0 一月 11 16:34 tist ``` <a name="users-groups"></a> ## User and Group Management <a name="users"></a> ### Users [[ Back to Table of Contents ]](#toc) `users` shows all users currently logged in. Note that a user can be logged in multiple times if -- for instance -- they're connected via multiple `ssh` sessions. ``` \[ andrew@pc01 ~ \]$ 用戶 安德魯·科林·科林·科林·科林·科林·克里希納·克里希納 ``` To see all users (even those not logged in), check `/etc/passwd`. (**WARNING**: do not modify this file! You can corrupt your user accounts and make it impossible to log in to your system.) ``` \[ andrew@pc01 ~ \]$ alias au="cut -d: -f1 /etc/passwd \\ > |排序| uniq”&& au \_易於 一個廣告 安德魯... ``` Add a user with `useradd`: ``` \[ andrew@pc01 ~ \]$ sudo useradd aardvark && au \_易於 土豚 一個廣告... ``` Delete a user with `userdel`: ``` \[ andrew@pc01 ~ \]$ sudo userdel aardvark && au \_易於 一個廣告 安德魯... ``` [Change a user’s default shell, username, password, or group membership with `usermod`.](http://bit.ly/2D4upIg) <a name="groups"></a> ### Groups [[ Back to Table of Contents ]](#toc) `groups` shows all of the groups of which the current user is a member: ``` \[ andrew@pc01 ~ \]$ 組 andrew adm cdrom sudo dial plugdev lpadmin sambashare hadoop ``` To see all groups on the system, check `/etc/group`. (**DO NOT MODIFY** this file unless you know what you are doing.) ``` \[ andrew@pc01 ~ \]$ alias ag=“cut -d: -f1 /etc/group \\ > |排序”&& ag 管理員 一個廣告 安德魯... ``` Add a group with `groupadd`: ``` \[ andrew@pc01 ~ \]$ sudo groupadd aardvark && ag 土豚 管理員 一個廣告... ``` Delete a group with `groupdel`: ``` \[ andrew@pc01 ~ \]$ sudo groupdel aardvark && ag 管理員 一個廣告 安德魯... ``` [Change a group’s name, ID number, or password with `groupmod`.](https://linux.die.net/man/8/groupmod) <a name="text-processing"></a> ## Text Processing <a name="uniq-sort-diff-cmp"></a> ### `uniq / sort / diff / cmp` [[ Back to Table of Contents ]](#toc) `uniq` can print unique lines (default) or repeated lines: ``` \[ andrew@pc01 man \]$ printf "1\\n2\\n2" > a && \\ > printf "1\\n3\\n2" > b \[ andrew@pc01 人 \]$ uniq a 1 2 ``` `sort` will sort lines alphabetically / numerically: ``` \[ andrew@pc01 man \]$ 排序 b 1 2 3 ``` `diff` will report which lines differ between two files: ``` \[ andrew@pc01 人 \]$ diff ab 2c2 < 2 --- > 3 ``` `cmp` reports which bytes differ between two files: ``` \[andrew@pc01 人\]$ cmp ab ab 不同:字元 3,第 2 行 ``` <a name="cut-sed"></a> ### `cut / sed` [[ Back to Table of Contents ]](#toc) `cut` is usually used to cut a line into sections on some delimiter (good for CSV processing). `-d` specifies the delimiter and `-f` specifies the field index to print (starting with 1 for the first field): ``` \[ andrew@pc01 人 \]$ printf "137.99.234.23" > c \[ andrew@pc01 man \]$ cut -d'.' c-f1 137 ``` `sed` is commonly used to replace a string with another string in a file: ``` \[ andrew@pc01 man \]$ echo "舊" | sed s/舊/新/ 新的 ``` ...but `sed` is an extremely powerful utility, and cannot be properly summarised here. It’s actually Turing-complete, so it can do anything that any other programming language can do. `sed` can find and replace based on regular expressions, selectively print lines of a file which match or contain a certain pattern, edit text files in-place and non-interactively, and much more. A few good tutorials on `sed` include: - [https://www.tutorialspoint.com/sed/](https://www.tutorialspoint.com/sed/) - [http://www.grymoire.com/Unix/Sed.html](http://www.grymoire.com/Unix/Sed.html) - [https://www.computerhope.com/unix/used.htm](https://www.computerhope.com/unix/used.htm) <a name="pattern-matching"></a> ## Pattern Matching <a name="grep"></a> ### `grep` [[ Back to Table of Contents ]](#toc) The name `grep` comes from `g`/`re`/`p` (search `g`lobally for a `r`egular `e`xpression and `p`rint it); it’s used for finding text in files. `grep` is used to find lines of a file which match some pattern: ``` \[ andrew@pc01 ~ \]$ grep -e " *.fi.* " /etc/profile /etc/profile:Bourne shell 的系統範圍 .profile 檔案 (sh(1)) =================================================== ``` # The file bash.bashrc already sets the default PS1. ``` ``` fi ``` ``` fi ``` … ``` ...or contain some word: ``` \[ andrew@pc01 ~ \]$ grep "andrew" /etc/passwd 安德魯:x:1000:1000:安德魯,,,:/home/andrew:/bin/bash ``` `grep` is usually the go-to choice for simply finding matching lines in a file, if you’re planning on allowing some other program to handle those lines (or if you just want to view them). `grep` allows for (`-E`) use of extended regular expressions, (`-F`) matching any one of multiple strings at once, and (`-r`) recursively searching files within a directory. These flags used to be implemented as separate commands (`egrep`, `fgrep`, and `rgrep`, respectively), but those commands are now deprecated. > **Bonus**: [see the origins of the names of a few famous `bash` commands](https://kb.iu.edu/d/abnd) <a name="awk"></a> ### `awk` [[ Back to Table of Contents ]](#toc) `awk` is a pattern-matching language built around reading and manipulating delimited data files, like CSV files. As a rule of thumb, `grep` is good for finding strings and patterns in files, `sed` is good for one-to-one replacement of strings in files, and `awk` is good for extracting strings and patterns from files and analysing them. As an example of what `awk` can do, here’s a file containing two columns of data: ``` \[ andrew@pc01 ~ \]$ printf "A 10\\nB 20\\nC 60" > 文件 ``` Loop over the lines, add the number to sum, increment count, print the average: ``` \[ andrew@pc01 ~ \]$ awk 'BEGIN {sum=0;計數=0; OFS=“”} {sum+=$2; count++} END {print "平均值:", sum/count}' 文件 平均:30 ``` `sed` and `awk` are both Turing-complete languages. There have been multiple books written about each of them. They can be extremely useful with pattern matching and text processing. I really don’t have enough space here to do either of them justice. Go read more about them! > **Bonus**: [learn about some of the differences between `sed`, `grep`, and `awk`](http://bit.ly/2AI3IaN) <a name="ssh"></a> ## Copying Files Over `ssh` <a name="ssh-scp"></a> ### `ssh / scp` [[ Back to Table of Contents ]](#toc) `ssh` is how Unix-based machines connect to each other over a network: ``` \[ andrew@pc01 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 12:30:52,來自 137.xxx.xxx.199 ``` Notice that my prompt has changed as I’m now on a different machine: ``` \[ andrew@pc02 ~ \]$ 退出 登出 與 137.xxx.xxx.89 的連線已關閉。 ``` Create a file on machine 1: ``` \[ andrew@pc01 ~ \]$ echo "你好" > 你好 ``` Copy it to machine 2 using `scp` (secure copy; note that `scp` uses `–P` for a port #, `ssh` uses `–p`) ``` \[ andrew@pc01 ~ \]$ scp –P你好安德魯@137.xxx.xxx.89:~ 你好 100% 0 0.0KB/秒 00:00 ``` `ssh` into machine 2: ``` \[ andrew@pc02 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 22:47:37,來自 137.xxx.xxx.79 ``` The file’s there! ``` \[ andrew@pc02 ~ \]$ ls 你好多xargs \[ andrew@pc02 ~ \]$ 貓你好 你好 ``` <a name="rsync"></a> ### `rsync` [[ Back to Table of Contents ]](#toc) `rsync` is a file-copying tool which minimises the amount of data copied by looking for deltas (changes) between files. Suppose we have two directories: `d`, with one file, and `s`, with two files: ``` \[ andrew@pc01 d \]$ ls && ls ../s f0 f0 f1 ``` Sync the directories (copying only missing data) with `rsync`: ``` \[ andrew@pc01 d \]$ rsync -off ../s/\* . 正在發送增量文件列表... ``` `d` now contains all files that `s` contains: ``` \[ andrew@pc01 d \]$ ls f0 f1 ``` `rsync` can be performed over `ssh` as well: ``` \[ andrew@pc02 r \]$ ls \[ andrew@pc02 r \]$ rsync -avz -e "ssh -p “ [email protected]:~/s/\* 。 接收增量檔案列表 f0 f1 發送 62 位元組 接收 150 位元組 141.33 位元組/秒 總大小為 0 加速率為 0.00 \[ andrew@pc02 r \]$ ls f0 f1 ``` <a name="long-running-processes"></a> ## Long-Running Processes <a name="yes-nohup-ps-kill"></a> ### `yes / nohup / ps / kill` [[ Back to Table of Contents ]](#toc) Sometimes, `ssh` connections can disconnect due to network or hardware problems. Any processes initialized through that connection will be “hung up” and terminate. Running a command with `nohup` insures that the command will not be hung up if the shell is closed or if the network connection fails. Run `yes` (continually outputs "y" until it’s killed) with `nohup`: ``` \[ andrew@pc01 ~ \]$ nohup 是 & \[1\]13173 ``` `ps` shows a list of the current user’s processes (note PID number 13173): ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' 13173 分/10 00:00:12 是 ``` _...log out and log back into this shell..._ The process has disappeared from `ps`! ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' ``` But it still appears in `top` and `htop` output: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' 13173 安德魯 20 0 4372 704 636 D 25.0 0.0 0:35.99 是 ``` Kill this process with `-9` followed by its process ID (PID) number: ``` \[ andrew@pc01 ~ \]$ 殺死 -9 13173 ``` It no longer appears in `top`, because it’s been killed: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' ``` <a name="cron"></a> ### `cron / crontab / >>` [[ Back to Table of Contents ]](#toc) `cron` provides an easy way of automating regular, scheduled tasks. You can edit your `cron` jobs with `crontab –e` (opens a text editor). Append the line: ``` - - - - - 日期 >> ~/datefile.txt ``` This will run the `date` command every minute, appending (with the `>>` operator) the output to a file: ``` \[ andrew@pc02 ~ \]$ head ~/datefile.txt 2019 年 1 月 12 日星期六 14:37:01 GMT 2019 年 1 月 12 日星期六

如何撰寫出色的 README 文件

可以說,對於任何開源專案來說,最重要的文件就是自述文件。一個好的自述文件不僅告訴人們該專案的用途和用途,還告訴人們他們如何使用該專案並為其做出貢獻。 如果您在撰寫自述文件時沒有充分解釋您的專案的用途或人們如何使用它,那麼它幾乎違背了開源的目的,因為其他開發人員不太可能參與或為其做出貢獻。 TL;DR - 太長?跳到最後並使用我的[模板](#template)。 什麼是自述文件? -------- 本質上,自述文件是一個單一的文字檔案( `.txt`或`.md` ),可作為專案或目錄的一站式文件。對於大多數開源專案來說,它通常是最明顯的文件和登陸頁面。即使自述文件的名稱全部大寫,也是為了吸引讀者的注意力並確保這是他們首先閱讀的內容。 有證據顯示自述文件的歷史可以追溯到 20 世紀 70 年代。我能找到的最古老的例子是 DEC PDP-10 計算機的[自述文件](http://pdp-10.trailing-edge.com/decus_20tap3_198111/01/decus/20-0079/readme.txt.html),日期為 1974 年 11 月 27 日。儘管自述文件名稱的起源存在爭議,但最流行的兩種理論是: 1. 帶有打孔卡的原始大型計算機的程式設計師會留下一疊帶有“READ ME!”的紙質指令。寫在前面。 2. 這個名字是向劉易斯·卡羅爾的《愛麗絲夢遊仙境》致敬,其中主角愛麗絲發現了一瓶標有“喝我”的藥水和標有“吃我”的蛋糕,這使她的體型發生了變化。 自述文件中應包含哪些內容? ------------- 好的,那麼一個很棒的自述文件該包含什麼內容呢?作為起點,我建議您包括以下關鍵內容: **1. 命名事物** 不要忘記為您的專案或功能命名。 GitHub 上有數量驚人的沒有名稱的專案。 **2. 介紹或總結** 寫一篇簡短的兩三行簡介,解釋你的專案的用途和用途。還要省略“簡介”、“摘要”或“概述”等標題 - 很明顯這是一個簡介。 **3. 先決條件** 在介紹之後立即加入一個部分,標題為列出任何想要使用該專案的人在開始之前可能需要的任何先決知識或工具。例如,如果它在最新版本的 Python 上執行,請告訴他們安裝 Python。這是一個例子: ``` Prerequisites Before you continue, ensure you have met the following requirements: * You have installed the latest version of Ruby. * You are using a Linux or Mac OS machine. Windows is not currently supported. * You have a basic understanding of graph theory. ``` **4. 如何安裝** 提供安裝步驟!令人驚訝的是,我遇到的許多專案只提供基本的使用說明,並期望您神奇地知道如何安裝它。如果需要多個步驟,請確保將安裝分解為編號的步驟。 **5. 如何使用該東西** 新增使用者安裝專案後如何使用該專案的步驟。如果您認為有用,請確保包含使用範例和解釋命令選項或標誌的參考。如果您在單獨的文件或網站中有更高級的文件,請從此處連結到該文件。 **6. 如何為事情做出貢獻** 提供為專案做出貢獻的步驟。或者,如果您希望人們在向您的專案貢獻拉取請求之前閱讀它,您可能希望在單獨的文件中建立貢獻者指南,並從自述文件連結到該指南。 **7. 加入貢獻者** 在作者部分感謝為該專案提供幫助的任何貢獻者。這是一種很好的方式,讓開源感覺像是團隊的努力,並感謝每個花時間做出貢獻的人。 **8. 加入致謝** 同樣,如果您使用了其他人的作品(程式碼、設計、圖像等),並且該作品具有需要確認的版權,您可能需要在此處加入。您還可以感謝為該專案提供幫助的任何其他開發商或機構。 **九、聯絡方式** 您可能不想這樣做,因為您是一個非常注重隱私的人,但如果有人有疑問、想要與您合作或對您的專案印象深刻並為您提供工作機會,那麼將您的聯絡方式放在前面是有意義的中心。 **10.新增許可證訊息** 如果適用,您肯定希望包含許可證資訊。除非您提供此軟體,否則依賴第三方軟體的新創公司和其他公司不太可能能夠使用您的專案。請造訪[Choosealicense.com](https://choosealicense.com/)或[opensource.org](https://opensource.org/licenses) ,以取得您可以使用的授權清單。 在您的自述文件中加入耀斑 🔥 -------------- 如果您確實想讓您的自述文件脫穎而出並且看起來具有視覺吸引力,您可以執行以下操作: - **新增徽標**:如果您的專案有徽標,請將其新增至自述文件的頂部。品牌使專案看起來更專業並幫助人們記住它。 - **新增徽章或盾牌**:您可以新增徽章和盾牌以反映專案的當前狀態、它使用的許可證以及它使用的任何依賴項是否是最新的。而且它們看起來很酷!您可以在[Shields.io](https://shields.io/)找到徽章清單或自行設計。 - **新增螢幕截圖**:有時一個簡單的螢幕截圖或一組螢幕截圖可以表達的內容遠遠超過一千個單字。但請注意,如果您確實使用螢幕截圖,則每次更新專案時都需要更新它們。 - **使用表情符號?** :現在很多專案似乎都使用表情符號,儘管是否使用它們取決於您。它們是為你的自述文件注入一點色彩和幽默的好方法,讓專案感覺更人性化。 如果您使用[所有貢獻者](https://allcontributors.org/)來感謝貢獻,您可以使用他們的[表情符號鍵](https://allcontributors.org/docs/en/emoji-key)來表示不同的貢獻類型: ``` * 🐛 for raising a bug ``` ``` * 💻 for submitting code ``` ``` * 📖 for docs contributions etc. ``` 這是 GitHub 徽章或盾牌的樣子,供參考(毫無疑問您以前見過它們!): ![Shields.io 徽章](https://thepracticaldev.s3.amazonaws.com/i/de766ouyuouxmjs2yvcm.png) 我的模板<a name="template"></a> --------------------------- 我建立了一個模板,涵蓋了本文中提出的大部分建議。請隨意分叉存儲庫,提出改進建議或根據自己的目的自訂它!您可以在 GitHub[上](https://github.com/scottydocs/README-template.md)找到我的模板。 資源 -- 如果您需要更多有關自述文件的建議,我還推薦以下資源: - Daniel Beck 在 2016 年 Write the Docs 的演講[「Write the Readable」](https://www.youtube.com/watch?v=2dAK42B7qtw) 。 - Lorna Jane Mitchell 在 API 文件 2019 中的演講[「Github 作為登陸頁面」](https://youtu.be/fXMN4X9B8Rg) 。 - 查看 Franck Abgrall 的[README 產生器](https://github.com/kefranabg/readme-md-generator)。 --- 原文出處:https://dev.to/scottydocs/how-to-write-a-kickass-readme-5af9

值得學習的 5 個 React 應用程式範例

我第一次開始編碼是在 14 歲時,當時我正在尋求建立我的第一家新創公司。我不知道從哪裡開始,最後選擇了一個名為 OpenCart 的熱門開源電子商務平台。經過大量時間和不眠之夜後,我發布了我的新創公司的第一個版本,用戶可以在其中交易和出售二手 DVD。幾個版本之後,我應用了該程式碼庫中的所有最佳實踐來建立一個自訂平台,如果沒有任何範例可供學習,我就無法做到這一點。 在這篇文章中,我收集了 5 個使用 React 建立的專案範例,可以幫助每個新手 (React) 開發人員提高技能。有時教程有點太慢或不夠複雜,而您只是想探索“真正的”程式碼庫。所有列出的專案都附帶自訂後端或使用開放 API 來獲取資料,因此您不必使用模擬資料。享受! TMDB 電影資料庫 ---------- 作為一名電影迷,我花了很多時間在 IMDb 上尋找節目或人物,但不幸的是他們不提供開放的 API。這就是電影資料庫 (TMDb) 的用武之地,它提供了一個[出色的開源 API](https://www.themoviedb.org/documentation/api?language=en-US) ,其中包含有關大多數電影和電視節目的資訊。這是一個流行的 API,可用於(愛好)專案或當您真正喜歡電影時。 [Stephen Kempin](https://twitter.com/s_kempin)的這個專案展示瞭如何在此 API 之上建立電影資料庫應用程式,使用 React 和 Twitter 的 typeahead.js 庫來實現自動建議搜尋功能。 https://github.com/SKempin/reactjs-tmdb-app 電子商務入門者 ------- 在過去的幾年裡,訂閱食品、刮鬍產品或衣服變得非常流行。使用這個開源產品,您可以建立自己的訂閱服務,而且它是全端的!(!!!)。使用 Crate,您可以獲得使用 React 建立的前端以及 Node.js 和 GraphQL 後端。如果您渴望創辦自己的公司,並且正在尋找好的材料來學習如何模組化程式碼或整合前端和後端,那麼一定要看看這個儲存庫。他們甚至使用[StoryBook](https://storybook.js.org/) ,因此您可以檢查該專案中使用的所有元件。 https://github.com/atulmy/crate 蘋果音樂克隆 ------ 您聽過 Apple Music、Spotify 或 Google 上的音樂嗎?該專案是第一個專案的克隆,甚至帶有您可以使用的後端。想建立自己的後端嗎?有說明可以您自己執行此操作。在前端,React 與 Redux 和 Redux Thunk 一起使用,為您提供了一個廣泛的範例來開始使用 Redux 進行狀態管理。該專案尚不支援 React Hooks,因此請考慮這是一個挑戰,看看是否可以重構它。 https://github.com/tvillarete/apple-music-js Slack 克隆 ---- 如果您是一家公司的開發人員,那麼您很有可能一直在使用 Slack 作為溝通工具。還有什麼比建立您每天使用的工具的克隆更好的學習方法呢? [Luke Jackson](https://twitter.com/lukejacksonn)的 Slack 克隆版本使用 React 和流行的產品[ChatKit](https://pusher.com/chatkit) ,可讓您輕鬆建立進階聊天應用程式。您可以透過請求 API 金鑰免費開始使用。發現任何錯誤並願意開始為開源做出貢獻嗎?此存儲庫中有開放的[初學者友好](https://github.com/pusher/react-slack-clone/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)票證。 https://github.com/lukejacksonn/react-slack-clone Hacker News 克隆 ------ [駭客新聞](https://news.ycombinator.com/)不僅是與程式設計和技術相關的新聞的重要來源。它也是開發人員中的經典,通常是展示新前端框架或語言的演示的起點。 [Clinton D'Annolfo](https://twitter.com/clintondannolfo)的這個特定專案透過在前端使用 React 和 GraphQL,以及在 Node.js 和 GraphQL 上執行的伺服器來實現這一目標。作為獎勵,加入了[Next.js](https://nextjs.org/)以支援伺服器端渲染 (SSR)。 https://github.com/clintonwoo/hackernews-react-graphql 您對這些專案有何看法?希望他們可以幫助您提高 React 技能,並且不要忘記留下任何反饋 😄! --- 原文出處:https://dev.to/gethackteam/5-examples-of-react-applications-to-learn-from-275b

如何編寫有用的 commit 訊息(我的提交訊息範本)

我們都曾在某個時刻經歷過有關 git commit 訊息的混亂。 ![XKCD - 隨著專案的拖延,我的 git 提交訊息的資訊量越來越少](https://thepracticaldev.s3.amazonaws.com/i/xn60rbbj7353tb5gtkh1.png) 我也不例外。我的部落格的提交訊息如下所示: ``` fixxxx stuff post post post post posts mmm posts front maddy Add chris oliver add syntax article add git patch article fix video video arty art art Fix links oops ``` 因為我部落格的 git 歷史記錄只有我自己看過,所以沒關係。我已經接受了我永遠無法在我的部落格中充分利用 git 的事實,而且我完全同意這一點。 不幸的是,有些人對待有多個貢獻者的真實專案就像我對待我的部落格一樣。我發現這種做法是無知而不是懶惰的結果。因此,我將分享一些關於如何在實際專案中使用提交訊息的技巧。 ### 為什麼要關心? 😤[我不在乎,跳到模板!](#the-final-template) 🚀 Git 是一個強大的工具,即使您只使用它來保存程式碼更改歷史記錄並且不利用其最強大的功能。 然而,你會發現你挖掘得越深,git 就會變得越強大。您還會發現 git 的許多最有用的功能都是在提交訊息有用的假設下運作的。 想想你上次使用`git blame`是什麼時候。如果您發現一條提交訊息,上面`fixed a bad bug` ,這會有幫助嗎?可能不是,您可能試圖找到有關您正在處理的程式碼的更多上下文;你需要解釋什麼以及為什麼。 Git 提交訊息必須包含每次更改背後的內容和原因,以便明天勇敢的 git 探索者能夠進入提交作者的頭腦。如果提交訊息不包含該訊息,為什麼還要寫一個?提交訊息只有在對將來某個時候試圖理解更改的人有用時才有用。 為了建立一個良好的提交訊息的模板,我將把提交訊息分成幾個部分。 ### 首先,主題行 在提交訊息中,第一行(有時稱為主題行)應與正文隔離。理想情況下,這一行總結了提交中所做的更改。 當我寫主題行時,我嘗試完成這句話,“這次提交將......” 例如,我可能會編寫一個主題行,內容類似於`Remove unused, commented code` 。這可以很好地結束我的句子:“此提交將刪除未使用的帶註釋的程式碼。” 在設定主題行格式時,需要記住一兩個規則。 首先,主題行中的第一個字元應大寫;這只是一個常見的約定。根據我的經驗,它還使閱讀一長串的單行提交清單變得更加容易。 其次,您的提交訊息不應超過五十個字元。這是因為 GitHub 等工具會將該行截斷為 50 個字元。因此,為了讓其他人能夠有效地瀏覽和理解你的主題行,你應該嘗試用五十個字符來總結整個變化。 我的提交訊息範本的第一行如下所示: `Summarize the change in less than 50 characters` ### 接下來,第一個正文“段落” 在某些提交中,主題行足以傳達整個想法。例如,如果您的提交將`Add a comma to the README` ,您可能不需要自己解釋。 然而,在大多數提交中,您的更改可能會受益於一些額外的上下文。我們不希望未來的開發人員在嘗試理解更改背後的原因時錯過上下文。 這就是訊息正文發揮作用的地方。我將正文分為“段落”,這些“段落”只是鬆散定義的由空格分隔的文字字串。它們可以是要點、句子或其他東西;重要的是它們從冷開始就易於閱讀和理解。 過去,我通常使用提交訊息正文的第一段來解釋我所做的事情。這些天,我已經不再關注*「什麼」* ,而是開始記錄*「為什麼」* 。 [Ben Orenstein 最近改變了我對提交訊息格式的看法](https://twitter.com/r00k/status/1175100703829909505?s=20): {% 推特 1175100703829909505 %} 因此,在這種情況下,我們想要引導我們做出改變*的原因*。 這是一個例子: ``` Refactor the coupon UI Because: - The old UI code is fairly slow - There were a few unused dependencies - The old UI has aged poorly ``` 這些「段落」的偉大之處在於只有一個格式規則:72 個字元換行。這更多的是一種遺留傳統,而不是任何實質的東西。然而,主要原因是這允許 git 縮進一些空間(假設最大字元限制為 80)。我建議遵循這條規則,儘管它並不總是嚴格必要的。 這是到目前為止的提交訊息範本: ``` Summarize the change in less than 50 characters Because: - Explain the reasons you made this change - Make a new bullet for each reason - Each line should be under 72 characters ``` ### 現在是第二正文“段落” 既然我們已經總結了更改並分享了進行更改的原因,那麼以較長的形式準確地解釋我們所做的事情可能是謹慎的做法。 我用第二個「段落」來更詳細地解釋我在更改中所做的事情,例如: ``` Refactor the coupon UI Because: - The old UI code is fairly slow - There were a few unused dependencies - The old UI has aged poorly I thought it was necessary to remove some of the old coupon UI code. Unfortunately, it has aged pretty poorly, and I think this refactor makes the code much easier to support in the long-run. Primarily, this commit improves the performance of the coupon component. It also removes some unused dependencies. ``` 提交正文的這一部分應該比 50 個字元的摘要更深入地解釋所做的事情。格式由您決定(只要您以 72 個字元換行即可)。 這是更新後的模板: ``` Summarize the change in less than 50 characters Because: - Explain the reasons you made this change - Make a new bullet for each reason - Each line should be under 72 characters Explain exactly what was done in this commit with more depth than the 50 character subject line. Remember to wrap at 72 characters! ``` ### 其他部分:附加註釋和合著者 此時,我們正在編寫有效且連貫的提交訊息。然而,有時提交訊息需要一些額外的註釋。這可以在最後一節中完成。 例如: ``` Refactor the coupon UI Because: - The old UI code is fairly slow - There were a few unused dependencies - The old UI has aged poorly I thought it was necessary to remove some of the old coupon UI code. Unfortunately, it has aged pretty poorly, and I think this refactor makes the code much easier to support in the long-run. Primarily, this commit improves the performance of the coupon component. It also removes some unused dependencies. These changes should resolve issue #1337. This commit removed the left-pad dependency, so please stop using it! Co-authored-by: nspinazz89 <[email protected]> ``` 在這個例子中我能夠: - 參考相關問題 - 加入一行以警告我刪除了依賴項 - 包含與我一起參與該提交的人員的引用 此時,任何查看此提交訊息的人都會知道: 1. 做了什麼一目了然 2. 為什麼需要改變 3. 有關已完成操作的詳細訊息 4. 有關變更的任何有用的詳細訊息 這使得我們的提交訊息對我們未來的自己和任何其他需要理解我們程式碼的開發人員來說更加有用。 即使您不同意我編寫提交訊息的方法,也很難否認我們必須編寫提交訊息,以便其他開發人員在閱讀我們的程式碼時能夠進入我們的視野。 我認為大多數人都同意“好”程式碼的標誌是可維護性,您可以透過編寫有助於其他人理解甚至將來更改您的程式碼的提交訊息來增強程式碼的可維護性。 ### 最終模板 ``` Summarize the change in less than 50 characters Because: - Explain the reasons you made this change - Make a new bullet for each reason - Each line should be under 72 characters Explain exactly what was done in this commit with more depth than the 50 character subject line. Remember to wrap at 72 characters! Include any additional notes, relevant links, or co-authors. ``` ### 還有更多... 這些天我寫了[很多文章](https://jh.codes),我經營一個[播客](https://www.devpath.fm),並且我已經開始發送一份關於我聽到的所有精彩故事的[時事通訊摘要](https://pages.convertkit.com/674caf55d4/f30f7753a7)。 您還可以在[Twitter](https://twitter.com/jakeherrington)上關注我,我在那裡製作一些愚蠢的表情包並談論如何成為開發人員。 --- 原文出處:https://dev.to/jacobherrington/how-to-write-useful-commit-messages-my-commit-message-template-20n9

如何將 .py 轉換為 .exe?一步步指導。

--- 標題: 如何將 .py 轉換為 .exe?一步步指導。 發表:真實 封面圖片:https://thepracticaldev.s3.amazonaws.com/i/3c27e6ni9a7preeeqo0v.jpg 描述:如何將 .py 轉換為 .exe 的說明 標籤: python, 初學者, oop, 學習 --- 自動 PY 到 EXE =========== 我們要使用的唯一工具是**Auto PY to EXE** ! **Auto PY to EXE**是一款令人驚嘆的應用程式,用於從專案中產生 .exe 文件,無論它是一個 .py 檔案還是任意數量的檔案。 該應用程式有一個漂亮的 GUI,如下所示: ![所有文字](https://warehouse-camo.cmh1.psfhosted.org/4fc81b16448c94eda8a266110b56284ca9883185/68747470733a2f2f692e696d6775722e636f6d2f6464304c43326e2e706e67) 如何開始 ---- 步驟一、安裝 ------ 使用 PyPI 安裝: 要安裝該應用程式,請在**cmd**中執行以下行: `pip install auto-py-to-exe` 若要開啟應用程式,請在**cmd**中執行此行: `auto-py-to-exe` 注意:如果您以這種方式安裝時遇到任何問題,或者您想從GitHub 安裝它,請前往\[主頁\] (https://pypi.org/project/auto-py-to-exe) 或觀看此說明影片「Auto PY to EXE」的開發者\[他自己\] (https://github.com/brentvollebregt)。 {% youtube OZSZHmWSOeM %} ### 欲了解更多附加訊息,請使用此 #### \[「使用 auto-py-to-exe 時的問題」\] (https://nitratine.net/blog/post/issues-when-using-auto-py-to-exe) 步驟 2. 轉換 -------- 您需要選擇幾個主要選項: 1. 選擇你的 .py 文件 2. 選擇“一個目錄”或“一個檔案”選項 3. 選擇其他文件 1. 選擇你的 .py 文件 -------------- 如果您有多個文件,請選擇一個來啟動程式。 2.1. 「一個目錄」選項 ------------- ![所有文字](https://thepracticaldev.s3.amazonaws.com/i/9mrf0i0cm0shdgoizjdz.JPG) 很簡單。當選擇“One Directory”選項時,“Auto PY to EXE”會將所有相依性放在**一個資料夾**中。您可以在“進階”選單中選擇輸出目錄。如果您有圖標和背景等媒體文件,如果您將媒體文件/資料夾放在輸出目錄中,那麼在 .exe 中使用它們應該不會有任何問題。 像這樣的東西: ![所有文字](https://thepracticaldev.s3.amazonaws.com/i/1a8b99f7f5gggq7fe7cv.JPG) 2.2. 「一個檔案」選項 ------------- ![所有文字](https://thepracticaldev.s3.amazonaws.com/i/cuq5tm4xsjnngoi6uqmc.JPG) 當選擇“One File”選項時,“Auto PY to EXE”將建立一個包含所有依賴項但**不包含媒體檔案的.exe 檔案**。如果您的程式只有**預設的 Windows GUI** ,沒有圖示、背景、媒體文件,或者您可以將媒體資料夾放在 .exe 文件附近,請隨意跳過以下說明。對於那些想要將媒體檔案打包到 .exe 檔案本身的人,請閱讀第 3 段。 3. 選擇其他文件 --------- 「Auto PY to EXE」中有一個名為「Additional Files」的選單,可讓您新增您選擇的檔案。但有一個問題。 「Auto PY to EXE」使用**pyinstaller**將資料解壓縮到臨時資料夾中,並將該目錄路徑儲存在 \_MEIPASS 環境變數中。您的專案將找不到必要的文件,因為路徑已更改,也不會看到新路徑。換句話說,如果選擇「一個檔案」選項,則在「其他檔案」功能表中選擇的檔案**將不會新增**到 .exe 檔案中。要解決此問題,您應該使用Auto PY to EXE 開發人員提供的程式碼\[此處\](https://nitratine.net/blog/post/issues-when-using-auto-py-to-exe/#debugging ) ``` def resource_path(relative_path): ``` ``` """ Get absolute path to resource, works for dev and for PyInstaller """ ``` ``` try: ``` ``` # PyInstaller creates a temp folder and stores path in _MEIPASS ``` ``` base_path = sys._MEIPASS ``` ``` except Exception: ``` ``` base_path = os.path.abspath(".") ``` ``` return os.path.join(base_path, relative_path) ``` 要在您的專案中使用此程式碼,請替換您*現在*擁有的媒體檔案的連結 例如: ``` setWindowIcon(QIcon('media\icons\logo.png')) ``` *和* ``` setWindowIcon(QIcon(resource_path('logo.png')) ``` 現在連結將被正確引用,並且所選檔案已成功打包到 .exe 檔案中。 用於比較: 之前可能有連結 ``` "C:\Users\User\PycharmProjects\media\icons\logo.png" ``` 之後可能有連結 ``` "C:\Users\User\AppData\Local\Temp\\_MEI34121\logo.png" ``` 按下**“轉換 .PY 至 .EXE”** ![所有文字](https://warehouse-camo.cmh1.psfhosted.org/d2f89e7dfcbbd3635e0f098b43dbc9df7c74b7a4/68747470733a2f2f692e696d6775722e636f6d2f663354456e5a492e706e67) 等待 ![所有文字](https://warehouse-camo.cmh1.psfhosted.org/a71bb45213b2285ac68eedc2994709c478deb12d/68747470733a2f2f692e696d6775722e636f6d2f4d6a644f4e63432e706e67) 步驟 3. 執行您的程式! ------------- 現在一切都完成了! 執行。測試一下。看看發生了什麼事。 確保一切正常。 #### 您建立了一個目錄 您需要的每個檔案都應該位於**單一目錄**中。 #### 你製作了一個文件 這樣你應該有**一個 .exe 檔**。如果您有需要並且正確完成,您的 .exe 檔案將包含其中的所有媒體。**您不需要任何帶有 .exe 檔案的媒體檔案/資料夾**即可正確顯示它們。 --- ### 聚苯乙烯 如果您對應加入哪些重要資訊有任何回饋或建議,請隨時告訴我! 本指南並未描述以各種可能的方式完成的每個可能的選項。 我希望您發現這些資訊有用! 祝您的專案順利! --- 原文出處:https://dev.to/eshleron/how-to-convert-py-to-exe-step-by-step-guide-3cfi

😻建立您自己的 CLI 版本的 MonkeyType 🙈

長話短說 ---- 在這個易於理解的教程中,您將學習如何在幾分鐘內建立自己的 MonkeyType CLI 版本。 😎 **您將學到什麼:✨** - 使用 Pythoncurses 模組建立具有**WPM**和**Accuracy**支援的強大打字 CLI 應用程式。 您準備好成為 CLI MonkeyTyper 了嗎? 😉 無論這是您的第一個 CLI 應用程式還是第 n 個應用程式。請隨意跟隨。 ![猴子在筆記型電腦上打字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unzjcx4ues1ot7m4h4hu.gif) --- 設定環境🙈 ----- > ℹ️不需要設定虛擬環境,我們不會使用任何外部相依性。 建立一個資料夾來保存專案的所有原始程式碼: ``` mkdir cli-monkeytype-python cd cli-monkeytype-python ``` 建立兩個新文件,我們將在其中編寫程式: ``` touch main.py typing_test.py ``` `main.py`檔案將作為我們應用程式的起點,而`typing_test.py`檔案將保存程式的所有邏輯。 > ℹ️對於Linux或Mac用戶,您不需要下載任何依賴項,我們將主要使用**curses** 、 **time**和**random**模組,這些模組都包含在Python標準庫中。 ⚠️**注意** > Windows 使用者可能必須安裝curses,因為它不包含在Windows 的Python 標準庫中。在繼續下一步之前,請確保已安裝它。 --- 讓我們來寫程式吧🐵 --------- > 💡 我們將在本節中研究應用程式的方法、大綱和實際編碼部分。 😵‍💫 ### 方法和概要👀 我們將在這裡採取不同的方法,而不是將所有程式碼都塞在`main`文件中。我們將把程式碼分成不同文件中的類別。 將有一個單獨的文件,其中包含一個負責封裝與打字測試相關的所有邏輯的類別。在主文件中,我們將呼叫此類的方法。聽起來,對吧?讓我們開始吧。 🚀 這是我們類別的骨架以及我們將要處理的所有方法。 ``` class TypingTest: def __init__(self, stdscr): pass def get_line_to_type(self): pass def display_wpm(self): pass def display_accuracy(self): pass def display_typed_chars(self): pass def display_details(self): pass def test_accuracy(self): pass def test_wpm(self): pass ``` 所有函數名稱都應該是不言自明的。如果您需要協助理解每個函數的作用,即使在查看了此大綱之後,為什麼還要閱讀這篇文章?只是開玩笑\*不是真的\*。 😏 > 🥱 這是一個適合初學者的應用程式。別擔心,一起編碼吧。 ### 真正的樂趣開始了! ![表演時間 GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i0r36i2t9mrygd14wtop.gif) 我們將從導入模組並編寫`__init__`方法開始。這將初始化程式執行所需的所有術語。 ``` import curses import random import time class TypingTest: def __init__(self, stdscr): self.stdscr = stdscr self.to_type_text = self.get_line_to_type() self.user_typed_text = [] self.wpm = 0 self.start_time = time.time() # Initialize color pairs curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) # --SNIP-- ``` `stdscr`用於控制終端螢幕,對於建立使用者可以看到其擊鍵的基於文字的使用者介面至關重要。 ⌨️ `get_line_to_type`方法取得一行文字供使用者鍵入。該文字儲存在`self.to_type_text`變數中。當他們鍵入時,他們輸入的字元將保存在`self.user_typed_text`清單中。我們使用列表是因為當使用者更正錯誤輸入的字元時,彈出最後一項會更容易。 初始每分鐘字數 (WPM) 分數設定為 0,我們記錄測驗的開始時間。我們也初始化了一些**顏色對**,根據它們是否正確來指示字元上的顏色。稍後,我們將根據使用者打字所需的時間來計算**WPM** 。 現在,新增以下功能的程式碼 > ℹ️ 確保在專案根目錄中建立一個名為`typing_texts.txt`的新文件,其中包含幾行文字。參考:[點這裡](https://github.com/shricodev/blogs/blob/main/cli-monkeytype-python/typing_texts.txt)。 ``` # --SNIP-- def get_line_to_type(self): with open("typing_texts.txt", "r", encoding="utf-8") as file: lines = file.readlines() return random.choice(lines).strip() def display_wpm(self): self.stdscr.addstr(1, 0, f"WPM: {self.wpm}", curses.color_pair(3)) def display_accuracy(self): self.stdscr.addstr( 2, 0, f"Accuracy: {self.test_accuracy()}%", curses.color_pair(3), ) def display_typed_chars(self): for i, char in enumerate(self.user_typed_text): correct_character = self.to_type_text[i] # Use color pair 1 if correct, else color pair 2. color = 1 if char == correct_character else 2 self.stdscr.addstr(0, i, char, curses.color_pair(color)) def display_details(self): self.stdscr.addstr(self.to_type_text) self.display_wpm() self.display_accuracy() self.display_typed_chars() # --SNIP-- ``` 讓我總結一下這些方法,它們非常簡單: 🎯 `get_line_to_type(self)` :從名為「typing\_texts.txt」的檔案中擷取刪除了尾隨空格的隨機行。 🎯 `display_wpm(self)` :當使用者鍵入時在螢幕上的第一行顯示 WPM。 🎯 `display_accuracy(self)` :在螢幕上第 2 行顯示**準確率百分比。**準確率由我們即將編寫的`test_accuracy()`方法計算。 🎯 `display_typed_chars(self)` :顯示使用者在螢幕上輸入的字符,突出顯示一個顏色對(顏色 1)中的正確字符和另一個顏色對(顏色 2)中的錯誤字符。 🎯 `display_details(self)` :它本質上是一個輔助函數,幫助顯示上面所有顯示函數的內容。 好的,現在我們已經編寫了這些顯示方法,讓我們實現實際的邏輯來測試準確性和 WPM 本身。 新增以下程式碼行: ``` # --SNIP-- def test_accuracy(self): total_characters = min(len(self.user_typed_text), len(self.to_type_text)) # If there are no typed chars, show accuracy 0. if total_characters == 0: return 0.0 matching_characters = 0 for current_char, target_char in zip(self.user_typed_text, self.to_type_text): if current_char == target_char: matching_characters += 1 matching_percentage = (matching_characters / total_characters) * 100 return matching_percentage def test_wpm(self): # getkey method by default is blocking. # We do not want to wait until the user types each char to check WPM. # Else the entire logic will be faulty. self.stdscr.nodelay(True) while True: # Since we have nodelay = True, if not using max(), # users might end up with time.time() equal to start_time, # resulting in 0 and potentially causing a zero-divisible error in the below line. time_elapsed = max(time.time() - self.start_time, 1) # Considering the average word length in English is 5 characters self.wpm = round((len(self.user_typed_text) / (time_elapsed / 60)) / 5) self.stdscr.clear() self.display_details() self.stdscr.refresh() # Exit the loop when the user types in the total length of the text. if len(self.user_typed_text) == len(self.to_type_text): self.stdscr.nodelay(False) break # We have `nodelay = True`, so we don't want to wait for the keystroke. # If we do not get a key, it will throw an exception # in the below lines when accessing the key. try: key = self.stdscr.getkey() except Exception: continue # Check if the key is a single character before using ord() if isinstance(key, str) and len(key) == 1: if ord(key) == 27: # ASCII value for ESC break # If the user has not typed anything reset to the current time if not self.user_typed_text: self.start_time = time.time() if key in ("KEY_BACKSPACE", "\b", "\x7f"): if len(self.user_typed_text) > 0: self.user_typed_text.pop() elif len(self.user_typed_text) < len(self.to_type_text): self.user_typed_text.append(key) ``` 🎯 `test_accuracy(self)` :透過將使用者輸入的字元與目標文字進行比較,計算並返回打字準確度(以百分比形式)。如果字元匹配,則將匹配字元的計數加1。最後,計算匹配的百分比。 🎯 `test_wpm(self)` :計算每分鐘字數(WPM)並即時更新顯示。我們用一個**公式**來計算WPM,這不是我想出來的,我從網路複製的。它追蹤使用者輸入的內容,處理退格鍵,並在使用者完成輸入目標文字或按**ESC**時停止。 偉大的!這就是我們的**TypingTest**類別。 🎉 > ✅ 我們編寫程式碼的方式可以幫助我們輕鬆地將程式碼匯入到任何未來的專案中,並使維護變得更加容易。 是時候測試我們的實作了。 🙈 在`main.py`檔案中,加入以下程式碼行: ``` from curses import wrapper from typing_test import TypingTest def main(stdscr): stdscr.clear() stdscr.addstr("Welcome to the typing speed test") stdscr.addstr("\nPress any key to continue!") while True: typing_test = TypingTest(stdscr) stdscr.getkey() typing_test.test_wpm() stdscr.addstr( 3, 0, "Congratulations! You have completed the test! Press any key to continue...", ) stdscr.nodelay(False) key = stdscr.getkey() # Check if the key is a single character before using ord() if isinstance(key, str) and len(key) == 1: if ord(key) == 27: # ASCII value for ESC break if __name__ == "__main__": wrapper(main) ``` > 💡 注意:我們從curses `wrapper`方法內的main 函數,該函數處理curses 模組的初始化和清理。 在 main 中,我們建立**TypingTest**類別的實例並在無限循環中執行測試,這讓使用者可以繼續執行測試,直到他們決定按**ESC**退出。 讓我們看看它的實際效果。 🔥 ![打字測試演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0eregzw35f8yq66blja.gif) > 🫵 如果你已經做到了這一步,我想指派一個小任務給你。目前,我們正在從文件中隨機選擇文字進行輸入。我希望您從網路上**抓取**輸入文字並使用該內容。請隨意在我的儲存庫中開啟包含您的變更的拉取請求。 > 如果您需要幫助,我已經參與了一個類似的 Python 抓取專案。請隨意[檢查](https://github.com/shricodev/IPO-Monitor-NEPSE)一下。 --- **包起來!** 🐒 ---------- 到目前為止,您已經建立了一個 Python CLI 應用程式來測試您在終端機中的打字速度。 本文的記錄原始碼可在此處取得: https://github.com/shricodev/blogs/tree/main/cli-monkeytype-python 非常感謝您的閱讀! 🎉🫡 > 在下面的評論部分寫下你的想法。 👇 {% cta https://twitter.com/shricodev %} 在 Twitter 上追蹤我 🐥 {% endcta %} {% 嵌入 https://dev.to/shricodev %} --- 原文出處:https://dev.to/shricodev/build-your-own-cli-version-of-monkeytype-bm7