كيفية إنشاء تطبيق الويب الأول الخاص بك باستخدام Go

مرحبا يا هبر! أقدم إليكم ترجمة المقال "كيفية إنشاء تطبيق الويب الأول الخاص بك باستخدام Go" بواسطة Ayooluwa Isaiah.


هذا هو دليل تطبيق الويب Go الأول. سنقوم بإنشاء تطبيق إخباري يستخدم واجهة برمجة تطبيقات الأخبار لتلقي مقالات إخبارية حول موضوع محدد ، ونشره على خادم الإنتاج في النهاية.


يمكنك العثور على الشفرة الكاملة المستخدمة في هذا البرنامج التعليمي في مستودع جيثب هذا.


متطلبات


الشرط الوحيد لهذه المهمة هو أن تطبيق Go مثبت على جهاز الكمبيوتر الخاص بك وأنك على دراية تامة ببناءه وبنائه. إصدار Go الذي استخدمته لإنشاء التطبيق هو أيضًا الأحدث وقت كتابة هذا التقرير: 1.12.9 . لعرض الإصدار المثبت من Go ، استخدم الأمر go version .


إذا وجدت هذه المهمة صعبة للغاية بالنسبة لك ، فانتقل إلى درس اللغة التمهيدية السابق ، والذي سيساعدك على البدء.


لذلك دعونا نبدأ!


نحن استنساخ مستودع بدء ملف على جيثب وقرص cd في الدليل الذي تم إنشاؤه. لدينا ثلاثة ملفات رئيسية: في ملف main.go كل رمز Go لهذه المهمة. يعد ملف index.html هو القالب الذي سيتم إرساله إلى المستعرض ، التطبيق في assets/styles.css .


إنشاء خادم الويب الأساسي


لنبدأ بإنشاء خادم أساسي يرسل النص "Hello World!" إلى المتصفح عند تنفيذ طلب GET على جذر الخادم. قم بتغيير ملف main.go :


 package main import ( "net/http" "os" ) func indexHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<h1>Hello World!</h1>")) } func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

السطر الأول من package main - يعلن أن الكود الموجود في ملف main.go إلى الحزمة الرئيسية. بعد ذلك ، قمنا باستيراد الحزمة net/http ، والتي توفر تطبيقات HTTP العميل والخادم للاستخدام في تطبيقنا. هذه الحزمة جزء من المكتبة القياسية ويتم تضمينها مع كل تثبيت Go.


في الوظيفة main ، يقوم http.NewServeMux() بإنشاء مُضاعِف طلب HTTP جديد ويعينه للمتغير mux . بشكل أساسي ، يطابق مُضاعِف الطلب عنوان URL للطلبات الواردة إلى قائمة المسارات المسجلة ويستدعي المعالج المناسب للمسار كلما تم العثور على تطابق.


بعد ذلك ، نسجل أول وظيفة معالج لدينا لمسار الجذر / . وظيفة المعالج هذه هي الوسيطة الثانية لـ HandleFunc دومًا func (w http.ResponseWriter, r * http.Request) التوقيع func (w http.ResponseWriter, r * http.Request) .


إذا نظرت إلى دالة indexHandler ، فسترى أن لديها مثل هذا التوقيع ، مما يجعلها وسيطة ثانية صالحة لـ HandleFunc . المعلمة w هي البنية التي نستخدمها لإرسال ردود على طلب HTTP. ينفذ الأسلوب Write() ، الذي يأخذ شريحة بايت ويكتب البيانات المدمجة كجزء من استجابة HTTP.


من ناحية أخرى ، تمثل المعلمة r طلب HTTP الذي تم استلامه من العميل. هذه هي الطريقة التي نصل بها إلى البيانات المرسلة بواسطة متصفح الويب على الخادم. نحن لا نستخدمها هنا بعد ، لكننا سنستخدمها لاحقًا.


أخيرًا ، لدينا طريقة http.ListenAndServe() ، والتي تبدأ الخادم على المنفذ 3000 إذا لم يتم تعيين المنفذ بواسطة البيئة. لا تتردد في استخدام منفذ مختلف إذا تم استخدام 3000 على جهاز الكمبيوتر الخاص بك.


ثم قم بتجميع وتنفيذ الكود الذي كتبته للتو:


 go run main.go 

إذا ذهبت إلى http: // localhost: 3000 في متصفحك ، فسترى النص "Hello World!".


متصفح شجاع يظهر نص Hello World


الذهاب القوالب


دعونا نلقي نظرة على أساسيات templating في الذهاب. إذا كنت معتادًا على القوالب بلغات أخرى ، فيجب أن يكون ذلك سهلاً بدرجة كافية لفهمها.


توفر القوالب طريقة سهلة لتخصيص إخراج تطبيق الويب الخاص بك وفقًا للطريق دون الحاجة إلى كتابة نفس الكود في أماكن مختلفة. على سبيل المثال ، يمكننا إنشاء قالب لشريط التنقل واستخدامه في جميع صفحات الموقع دون تكرار الشفرة. بالإضافة إلى ذلك ، سنحصل أيضًا على فرصة لإضافة بعض المنطق الأساسي إلى صفحات الويب الخاصة بنا.


يوفر Go لمكتبتي قوالب في مكتبته القياسية: text/template و html/template . يوفر كلاهما نفس الواجهة ، ومع ذلك يتم استخدام حزمة html/template لإنشاء إخراج HTML محمي ضد حقن الشفرة ، لذلك سوف نستخدمها هنا.


استيراد هذه الحزمة إلى ملف main.go الخاص بك واستخدامها على النحو التالي:


 package main import ( "html/template" "net/http" "os" ) var tpl = template.Must(template.ParseFiles("index.html")) func indexHandler(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, nil) } func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

tpl هو متغير مستوى الحزمة يشير إلى تعريف القالب من الملفات المقدمة. يقوم template.ParseFiles ParseFiles باستدعاء ملف index.html في جذر دليل مشروعنا والتحقق من صحته.


نلف template.ParseFiles استدعاء ملف template.ParseFiles في template.Must أن يتسبب الرمز في حالة من الذعر عند حدوث خطأ. سبب ذعرنا هنا بدلاً من محاولة معالجة الخطأ هو أنه من غير المنطقي الاستمرار في تنفيذ التعليمات البرمجية إذا كان لدينا قالب غير صالح. هذه مشكلة تحتاج إلى إصلاح قبل محاولة إعادة تشغيل الخادم.


في دالة indexHandler نقوم بتنفيذ القالب الذي تم إنشاؤه مسبقًا من خلال توفير indexHandler : حيث نريد كتابة المخرجات والبيانات التي نريد نقلها إلى القالب.


في الحالة أعلاه ، نكتب الإخراج إلى واجهة ResponseWriter ، ولأنه ليس لدينا بيانات لتمريرها إلى القالب الخاص بنا في الوقت الحالي ، يتم تمرير nil كوسيطة ثانية.


أوقف عملية التشغيل في الجهاز الطرفي باستخدام Ctrl-C وابدأ تشغيله مرة أخرى باستخدام go run main.go ، ثم قم بتحديث المتصفح. يجب أن ترى النص "News App Demo" على الصفحة كما هو موضح أدناه:


متصفح شجاع يظهر أخبار التطبيق التجريبي النص


أضف شريط تنقل إلى الصفحة


استبدل محتويات <body> في ملف index.html كما هو موضح أدناه:


 <main> <header> <a class="logo" href="/">News Demo</a> <form action="/search" method="GET"> <input autofocus class="search-input" value="" placeholder="Enter a news topic" type="search" name="q"> </form> <a href="https://github.com/freshman-tech/news" class="button github-button">View on Github</a> </header> </main> 

ثم أعد تشغيل الخادم وقم بتحديث المتصفح. يجب أن ترى شيئًا مشابهًا لهذا:


متصفح يعرض شريط التنقل غير المستوي


العمل مع الملفات الثابتة


يرجى ملاحظة أن شريط التنقل الذي أضفناه أعلاه لا يحتوي على أنماط ، على الرغم من أننا حددناها بالفعل في <head> وثيقتنا.


وذلك لأن المسار / الواقع يطابق جميع المسارات التي لم تتم معالجتها في أي مكان آخر. لذلك ، إذا ذهبت إلى http: // localhost: 3000 / الأصول / style.css ، فستظل تحصل على الصفحة الرئيسية للعرض التجريبي للأخبار بدلاً من ملف CSS لأن المسار /assets/style.css لم يتم /assets/style.css وجه التحديد.


لكن الحاجة إلى الإعلان عن معالجات صريحة لجميع ملفاتنا الثابتة غير واقعية ولا يمكن توسيع نطاقها. لحسن الحظ ، يمكننا إنشاء معالج واحد لخدمة جميع الموارد الثابتة.


أول شيء يجب فعله هو إنشاء مثيل لكائن خادم الملفات ، مع تمرير الدليل الذي توجد به جميع ملفاتنا الثابتة:


 fs := http.FileServer(http.Dir("assets")) 

بعد ذلك ، نحتاج إلى إخبار جهاز التوجيه الخاص بنا باستخدام كائن خادم الملفات هذا لجميع المسارات التي تبدأ بـ /assets/ البادئة:


 mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) 

الآن معًا:


 // main.go //   func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() //     fs := http.FileServer(http.Dir("assets")) mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

إعادة تشغيل الخادم وتحديث المتصفح. يجب تشغيل الأنماط كما هو موضح أدناه:


متصفح شجاع يظهر شريط التنقل على طراز



لنقم بإنشاء مسار يتعامل مع استعلامات البحث عن المقالات الإخبارية. سنستخدم واجهة برمجة التطبيقات للأخبار لمعالجة الطلبات ، لذلك تحتاج إلى التسجيل للحصول على مفتاح API مجاني هنا .


يتوقع هذا المسار معلمتين للاستعلام: q تمثل استعلام المستخدم ، page استخدام page للتمرير خلال النتائج. معلمة page هذه اختيارية. إذا لم يتم تضمينه في عنوان URL ، فنحن نفترض ببساطة أن رقم صفحة النتائج مضبوط على "1".


أضف المعالج التالي ضمن indexHandler إلى ملف main.go :


 func searchHandler(w http.ResponseWriter, r *http.Request) { u, err := url.Parse(r.URL.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Internal server error")) return } params := u.Query() searchKey := params.Get("q") page := params.Get("page") if page == "" { page = "1" } fmt.Println("Search Query is: ", searchKey) fmt.Println("Results page is: ", page) } 

الرمز أعلاه يستخلص معلمات q page من عنوان URL للطلب ويعرض كلاهما في الجهاز.


ثم سجل وظيفة searchHandler مسار /search ، كما هو موضح أدناه:


 func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() fs := http.FileServer(http.Dir("assets")) mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) // Add the next line mux.HandleFunc("/search", searchHandler) mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

تذكر استيراد حزم fmt و net/url من أعلاه:


 import ( "fmt" "html/template" "net/http" "net/url" "os" ) 

أعد تشغيل الخادم الآن ، وأدخل الاستعلام في حقل البحث وتحقق من الجهاز. يجب أن ترى طلبك في المحطة ، كما هو موضح أدناه:




إنشاء نموذج البيانات


عندما نقدم طلبًا إلى نقطة نهاية News API/everything ، نتوقع استجابة json بالتنسيق التالي:


 { "status": "ok", "totalResults": 4661, "articles": [ { "source": { "id": null, "name": "Gizmodo.com" }, "author": "Jennings Brown", "title": "World's Dumbest Bitcoin Scammer Tries to Scam Bitcoin Educator, Gets Scammed in The Process", "description": "Ben Perrin is a Canadian cryptocurrency enthusiast and educator who hosts a bitcoin show on YouTube. This is immediately apparent after a quick a look at all his social media. Ten seconds of viewing on of his videos will show that he is knowledgeable about di…", "url": "https://gizmodo.com/worlds-dumbest-bitcoin-scammer-tries-to-scam-bitcoin-ed-1837032058", "urlToImage": "https://i.kinja-img.com/gawker-media/image/upload/s--uLIW_Oxp--/c_fill,fl_progressive,g_center,h_900,q_80,w_1600/s4us4gembzxlsjrkmnbi.png", "publishedAt": "2019-08-07T16:30:00Z", "content": "Ben Perrin is a Canadian cryptocurrency enthusiast and educator who hosts a bitcoin show on YouTube. This is immediately apparent after a quick a look at all his social media. Ten seconds of viewing on of his videos will show that he is knowledgeable about..." } ] } 

للعمل مع هذه البيانات في Go ، نحتاج إلى إنشاء هيكل يعكس البيانات عند فك ترميز نص الاستجابة. بالطبع ، يمكنك القيام بذلك يدويًا ، لكنني أفضل استخدام موقع JSON-to-Go ، مما يجعل هذه العملية سهلة للغاية. يقوم بإنشاء بنية Go (مع علامات) ستعمل مع JSON.


كل ما عليك فعله هو نسخ كائن JSON ولصقه في الحقل المحدد بعلامة JSON ، ثم انسخ الإخراج والصقه في الكود. إليك ما نحصل عليه لكائن JSON أعلاه:


 type AutoGenerated struct { Status string `json:"status"` TotalResults int `json:"totalResults"` Articles []struct { Source struct { ID interface{} `json:"id"` Name string `json:"name"` } `json:"source"` Author string `json:"author"` Title string `json:"title"` Description string `json:"description"` URL string `json:"url"` URLToImage string `json:"urlToImage"` PublishedAt time.Time `json:"publishedAt"` Content string `json:"content"` } `json:"articles"` } 

متصفح شجاع يعرض أداة JSON to Go


لقد قمت بإجراء العديد من التغييرات على بنية AutoGenerated عن طريق فصل جزء Articles إلى هيكله وتحديث اسم الهيكل. الصق إعلان متغير main.go التالي في main.go وأضف حزمة time إلى الاستيراد الخاص بك:


 type Source struct { ID interface{} `json:"id"` Name string `json:"name"` } type Article struct { Source Source `json:"source"` Author string `json:"author"` Title string `json:"title"` Description string `json:"description"` URL string `json:"url"` URLToImage string `json:"urlToImage"` PublishedAt time.Time `json:"publishedAt"` Content string `json:"content"` } type Results struct { Status string `json:"status"` TotalResults int `json:"totalResults"` Articles []Article `json:"articles"` } 

كما تعلم ، يتطلب Go أن تبدأ جميع الحقول المصدرة في الهيكل بحرف كبير. ومع ذلك ، من المعتاد تمثيل حقول JSON باستخدام camelCase أو snake_case ، والتي لا تبدأ بحرف كبير.


لذلك ، نستخدم علامات حقل الهيكل مثل json:"id" لعرض حقل الهيكل بشكل صريح في حقل JSON ، كما هو موضح أعلاه. كما يسمح لك باستخدام أسماء مختلفة تمامًا لحقل الهيكل وحقل json المقابل ، إذا لزم الأمر.


أخيرًا ، لنقم بإنشاء نوع مختلف من البنية لكل استعلام بحث. أضف هذا أدناه هيكل Results في main.go :


 type Search struct { SearchKey string NextPage int TotalPages int Results Results } 

تمثل هذه البنية كل استعلام بحث قدمه المستخدم. SearchKey هو الاستعلام نفسه ، يسمح NextPage الحقل TotalPages بالتمرير خلال النتائج و TotalPages - إجمالي عدد صفحات نتائج الاستعلام ، Results - الصفحة الحالية لنتائج الاستعلام.


إرسال طلب باستخدام واجهة برمجة التطبيقات للأخبار وتقديم النتائج


الآن وبعد أن أصبح لدينا نموذج البيانات لتطبيقنا ، دعونا نواصل تقديم الطلبات إلى News API ، ثم نعرض النتائج على الصفحة.


نظرًا لأن واجهة برمجة تطبيقات الأخبار تتطلب مفتاح API ، فنحن بحاجة إلى إيجاد طريقة لتمريرها في تطبيقنا دون الترميز الثابت في الشفرة. متغيرات البيئة هي نهج شائع ، لكنني قررت استخدام علامات سطر الأوامر بدلاً من ذلك. يوفر Go حزمة إشارة تدعم التحليل الأساسي لأعلام سطر الأوامر ، وهذا ما سنستخدمه هنا.


أولاً قم بتعريف متغير apiKey جديد ضمن متغير apiKey :


 var apiKey *string 

ثم استخدمه في الوظيفة main كما يلي:


 func main() { apiKey = flag.String("apikey", "", "Newsapi.org access key") flag.Parse() if *apiKey == "" { log.Fatal("apiKey must be set") } //    } 

نحن هنا نسميها flag.String() الطريقة ، التي تسمح لنا بتعريف علامة السلسلة. الوسيطة الأولى لهذه الطريقة هي اسم العلامة ، والثانية هي القيمة الافتراضية ، والثالثة هي وصف الاستخدام.


بعد تحديد جميع العلامات ، تحتاج إلى استدعاء flag.Parse() فعليًا. أخيرًا ، نظرًا لأن apikey هو مكون مطلوب لهذا التطبيق ، فنحن نتأكد من تعطل البرنامج إذا لم يتم تعيين هذه العلامة أثناء تنفيذ البرنامج.


تأكد من إضافة حزمة flag إلى الاستيراد الخاص بك ، ثم apikey تشغيل الخادم وتمرير علامة apikey المطلوبة ، كما هو موضح أدناه:


 go run main.go -apikey=<your newsapi access key> 

بعد ذلك ، دعونا نستمر في searchHandler بحيث يتم إرسال استعلام بحث المستخدم إلى newsapi.org ويتم عرض النتائج في قالبنا .


استبدل المكالمات اثنين إلى الأسلوب fmt.Println() في نهاية دالة searchHandler التعليمة البرمجية التالية:


 func searchHandler(w http.ResponseWriter, r *http.Request) { // beginning of the function search := &Search{} search.SearchKey = searchKey next, err := strconv.Atoi(page) if err != nil { http.Error(w, "Unexpected server error", http.StatusInternalServerError) return } search.NextPage = next pageSize := 20 endpoint := fmt.Sprintf("https://newsapi.org/v2/everything?q=%s&pageSize=%d&page=%d&apiKey=%s&sortBy=publishedAt&language=en", url.QueryEscape(search.SearchKey), pageSize, search.NextPage, *apiKey) resp, err := http.Get(endpoint) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode != 200 { w.WriteHeader(http.StatusInternalServerError) return } err = json.NewDecoder(resp.Body).Decode(&search.Results) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize))) err = tpl.Execute(w, search) if err != nil { w.WriteHeader(http.StatusInternalServerError) } } 

أولاً ، نقوم بإنشاء مثيل جديد لهيكل Search SearchKey بتعيين قيمة حقل SearchKey إلى قيمة معلمة URL q في طلب HTTP.


بعد ذلك ، نقوم بتحويل متغير page إلى عدد صحيح ونعين النتيجة إلى حقل NextPage متغير search . ثم نقوم بإنشاء متغير pageSize وضبط قيمته على 20. هذا المتغير pageSize يمثل عدد النتائج التي سيعود API الأخبار في رده. يمكن أن تتراوح هذه القيمة من 0 إلى 100.


ثم نقوم بإنشاء نقطة النهاية باستخدام fmt.Sprintf() وتقديم طلب GET إليها. إذا لم تكن استجابة واجهة برمجة التطبيقات للأخبار 200 موافق ، فسنقوم بإرجاع خطأ خادم عام إلى العميل. خلاف ذلك ، يتم تحليل نص الاستجابة في search.Results . search.Results .


ثم نحسب إجمالي عدد الصفحات بتقسيم حقل TotalResults pageSize . على سبيل المثال ، إذا كان الاستعلام يُرجع 100 نتيجة ، ولم نعرض سوى 20 نتيجة في كل مرة ، فسوف نحتاج إلى التمرير بخمس صفحات لرؤية كل 100 نتيجة لهذا الاستعلام.


بعد ذلك ، نقدم النموذج الخاص بنا ونمرر متغير search كواجهة بيانات. هذا يتيح لنا الوصول إلى البيانات من كائن JSON في قالبنا ، كما سترى.


قبل الانتقال إلى index.html ، تأكد من تحديث عمليات الاستيراد كما هو موضح أدناه:


 import ( "encoding/json" "flag" "fmt" "html/template" "log" "math" "net/http" "net/url" "os" "strconv" "time" ) 

دعنا نواصل عرض النتائج على الصفحة عن طريق تغيير ملف index.html النحو التالي. أضف هذا تحت علامة <header> :


 <section class="container"> <ul class="search-results"> {{ range .Results.Articles }} <li class="news-article"> <div> <a target="_blank" rel="noreferrer noopener" href="{{.URL}}"> <h3 class="title">{{.Title }}</h3> </a> <p class="description">{{ .Description }}</p> <div class="metadata"> <p class="source">{{ .Source.Name }}</p> <time class="published-date">{{ .PublishedAt }}</time> </div> </div> <img class="article-image" src="{{ .URLToImage }}"> </li> {{ end }} </ul> </section> 

للوصول إلى حقل البنية في القالب ، نستخدم عامل النقطة. يشير عامل التشغيل هذا إلى كائن بنية (في هذه الحالة ، search ) ، ثم داخل القالب نحدد ببساطة اسم الحقل (مثل {{.Results}} . النتائج) {{.Results}} ).


تسمح لنا range بالتكرار على شريحة في Go وإخراج بعض HTML لكل عنصر في الشريحة. هنا ، نقوم بالتكرار على شريحة بنيات Article الموجودة في حقل Articles ونعرض HTML في كل تكرار.


أعد تشغيل الخادم ، حدّث المتصفح وابحث عن الأخبار حول موضوع شائع. يجب أن تحصل على قائمة تضم 20 نتيجة لكل صفحة ، كما هو موضح في لقطة الشاشة أدناه.


متصفح يعرض قوائم الأخبار


حفظ استعلام البحث في الخارجية


لاحظ أن استعلام البحث يختفي من الإدخال عند تحديث الصفحة بالنتائج. من الناحية المثالية ، يجب الاحتفاظ بالاستعلام حتى يقوم المستخدم بإجراء بحث جديد. إليك طريقة عمل بحث Google ، على سبيل المثال.


يمكننا إصلاح ذلك بسهولة عن طريق تحديث سمة value علامة input في ملف index.html بنا على النحو التالي:


 <input autofocus class="search-input" value="{{ .SearchKey }}" placeholder="Enter a news topic" type="search" name="q"> 

أعد تشغيل المتصفح وأجري بحثًا جديدًا. سيتم حفظ استعلام البحث كما هو موضح أدناه:



تنسيق تاريخ النشر


إذا نظرت إلى التاريخ في كل مقالة ، فسترى أن قراءتها سيئة. يُظهر الإخراج الحالي كيف تُرجع واجهة برمجة التطبيقات للأخبار تاريخ نشر المقال. لكن يمكننا بسهولة تغيير هذا عن طريق إضافة طريقة إلى بنية Article واستخدامها لتنسيق التاريخ بدلاً من استخدام القيمة الافتراضية.


دعنا نضيف الكود التالي مباشرة أسفل هيكل Article في main.go :


 func (a *Article) FormatPublishedDate() string { year, month, day := a.PublishedAt.Date() return fmt.Sprintf("%v %d, %d", month, day, year) } 

هنا ، FormatPublishedDate إنشاء طريقة FormatPublishedDate جديدة في بنية Article ، وهذه الطريقة تقوم FormatPublishedDate في Article وإرجاع سلسلة بالتنسيق التالي: 10 2009 .


لاستخدام هذه الطريقة الجديدة في القالب الخاص بك ، .PublishedAt بـ .FormatPublishedDate في ملف index.html . ثم أعد تشغيل الخادم وكرر استعلام البحث السابق. سيؤدي ذلك إلى إخراج النتائج في وقت منسق بشكل صحيح ، كما هو موضح أدناه:


متصفح شجاع يظهر التاريخ المنسق بشكل صحيح


عرض العدد الإجمالي للنتائج.


لنقم بتحسين واجهة المستخدم لتطبيق الأخبار من خلال الإشارة إلى إجمالي عدد النتائج في أعلى الصفحة ، ثم عرض رسالة في حالة عدم العثور على نتائج لاستعلام معين.


كل ما عليك القيام به هو إضافة التعليمات البرمجية التالية كطفل لـ .container ، أعلى عنصر .search-results في ملف index.html :


 <div class="result-count"> {{ if (gt .Results.TotalResults 0)}} <p>About <strong>{{ .Results.TotalResults }}</strong> results were found.</p> {{ else if (ne .SearchKey "") and (eq .Results.TotalResults 0) }} <p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p> {{ end }} </div> 

Go , . gt , , TotalResults Results . , .


, SearchKey ( (ne .SearchKey "") ) TotalResults ( (eq .Results.TotalResults 0) ), «No results found».


, . «No results found».


متصفح لا تظهر نتائج العثور على رسالة


. , :


متصفح يعرض عدد النتائج في الجزء العلوي من الصفحة



20 , , .


Next , . , , Search main.go :


 func (s *Search) IsLastPage() bool { return s.NextPage >= s.TotalPages } 

, NextPage , TotalPages Search . , NextPage , . :


 func searchHandler(w http.ResponseWriter, r *http.Request) { //   search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize))) //   if  if ok := !search.IsLastPage(); ok { search.NextPage++ } //    } 

, , . .search-results index.html .


 <div class="pagination"> {{ if (ne .IsLastPage true) }} <a href="/search?q={{ .SearchKey }}&page={{ .NextPage }}" class="button next-page">Next</a> {{ end }} </div> 

, Next .


, href /search q , NextPage page .


Previous . , 1. , CurrentPage() Search , . IsLastPage :


 func (s *Search) CurrentPage() int { if s.NextPage == 1 { return s.NextPage } return s.NextPage - 1 } 

NextPage - 1 , , NextPage 1. , 1 . :


 func (s *Search) PreviousPage() int { return s.CurrentPage() - 1 } 

, Previous , 1. .pagination index.html :


 <div class="pagination"> {{ if (gt .NextPage 2) }} <a href="/search?q={{ .SearchKey }}&page={{ .PreviousPage }}" class="button previous-page">Previous</a> {{ end }} {{ if (ne .IsLastPage true) }} <a href="/search?q={{ .SearchKey }}&page={{ .NextPage }}" class="button next-page">Next</a> {{ end }} </div> 

. , :





, , , , .


index.html :


 <div class="result-count"> {{ if (gt .Results.TotalResults 0)}} <p>About <strong>{{ .Results.TotalResults }}</strong> results were found. You are on page <strong>{{ .CurrentPage }}</strong> of <strong> {{ .TotalPages }}</strong>.</p> {{ else if (ne .SearchKey "") and (eq .Results.TotalResults 0) }} <p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p> {{ end }} </div> 

, , .


متصفح يعرض الصفحة الحالية


Heroku


, , Heroku. , , . . freshman-news .


, Heroku . heroku login , Heroku.


, git- . , git init , , heroku git-. freshman-news .


 heroku git:remote -a freshman-news 

Procfile ( touch Procfile ) :


 web: bin/news-demo -apikey $NEWS_API_KEY 

GitHub Go, , go.mod , . , , .


 module github.com/freshman-tech/news-demo go 1.12.9 

Settings Heroku Reveal Config Vars . NEWS_API_KEY , .


المتغيرات التكوين Heroku


, Heroku :


 git add . git commit -m "Initial commit" git push heroku master 

https://__.herokuapp.com , .


استنتاج


News Go -. , Heroku.


, . - , , .


شكرا للقراءة!

Source: https://habr.com/ru/post/ar475390/


All Articles