هل تستخدم الحاويات... لتتبع أسعار هدايا عيد الميلاد؟

· 9 دقيقة قراءة

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

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

لماذا يتم أتمتة مراقبة الأسعار؟

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

  1. العناصر باهظة الثمن: نعلم جميعًا أن هدايا عيد الميلاد يمكن أن تصبح باهظة الثمن، خاصة عندما تكون الأدوات والألعاب مدرجة في قائمتك. يمكن أن يكون توفير المال على هذه المنتجات مرضيًا تمامًا مثل العثور على آخر قطعة علوية للشجرة في المخزون.
  2. الفحوصات اليومية: من لديه الوقت (أو الصبر) للتحقق من الأسعار كل يوم؟ ليس أنا!
  3. الأتمتة: احتضن روح العطاء في العطلة، وامنح نفسك هدية الكفاءة.
  4. الدقة: تضمن الأتمتة أن تكون عمليات التحقق من الأسعار دقيقة بقدر سطوع أنف رودولف.

التقنيات الأساسية

لبناء مساعد سانتا الصغير الخاص بي، قررت استخدام العديد من التقنيات الأساسية:

###حاويات

تشبه الحاويات غلاف عيد الميلاد للبرامج. يقومون بتجميع تطبيقاتك بكل الأشياء الجيدة (التبعيات) بحيث يسير كل شيء بسلاسة مثل ركوب الزلاجة في الثلج الطازج. Docker هو خيارنا الأمثل لإنشاء هذه الحاويات.

###بلازور

يعد Blazor إطارًا رائعًا لبناء تطبيقات الويب التفاعلية باستخدام .NET. يشبه الأمر استبدال ترانيم عيد الميلاد العامة بقائمة التشغيل الخاصة بك في العطلات، والتي تكون مصممة خصيصًا وفعالة وممتعة للغاية.

عامل الميناء يؤلف

Docker-Compose هو مدير عملياتنا في القطب الشمالي. فهو يساعدنا في الحفاظ على تشغيل جميع خدماتنا - مثل واجهة برمجة التطبيقات (API) والواجهة الأمامية لـ Blazor - معًا في وئام تام. فكر في الأمر كقائد لسيمفونية عطلتنا.

دليل خطوة بخطوة

الآن، دعونا نتعمق في ورشة عمل سانتا ونجعل هذا المشروع ينبض بالحياة!

الخطوة 1: إنشاء واجهة برمجة التطبيقات

1.1 إعداد المشروع

ارتدي قبعة سانتا للبرمجة وقم بإعداد مشروع ASP.NET Core Web API جديد. افتح جهازك الطرفي (يشبه إلى حد ما فتح تقويم المجيء الخاص بك) وقم بتشغيل:

dotnet new webapi -o PriceMonitorApi
cd PriceMonitorApi

يقوم هذا الأمر بإنشاء دليل جديد باسم PriceMonitorApi وإعداد مشروع Web API أساسي. تخيل أن الأمر يشبه إنشاء قاعدة متينة لمنزل خبز الزنجبيل الخاص بك.

1.2 إضافة HttpClient وحذف المكتباتبعد ذلك، أضف HttpClient ومكتبة لتحليل HTML. وستكون هذه مزلقتنا الموثوقة لجلب بيانات الأسعار وقراءتها.

dotnet add package HtmlAgilityPack

HtmlAgilityPack هو القزم الذي يساعدنا في تحليل مستندات HTML.

1.3 إنشاء النماذج

النماذج تشبه مخطط ألعابنا. لنقم بإنشاء نموذج لتمثيل معلومات المنتج:

namespace PriceMonitorApi.Models
{
    public class ProductInfo
    {
        public string Title { get; set; }
        public decimal Price { get; set; }
        public DateTime Date { get; set; }
    }
}

لقد أنشأنا للتو القالب المثالي لبياناتنا.

1.4 تنفيذ خدمة الكاشطة

تشبه خدمة الكشط لدينا مساعد سانتا الصغير، حيث تقوم بجلب المعلومات ومعالجتها لنا:

using HtmlAgilityPack;
using PriceMonitorApi.Models;
using System.Globalization;

namespace PriceMonitorApi.Services
{
    public class ScraperService
    {
        private readonly HttpClient _httpClient;

        public ScraperService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ProductInfo> ScrapeProductInfoAsync(string url)
        {
            var response = await _httpClient.GetStringAsync(url);

            var htmlDoc = new HtmlDocument();
            htmlDoc.LoadHtml(response);

            var title = htmlDoc.DocumentNode.SelectSingleNode("//h1[@class='product-title']").InnerText.Trim();
            var priceString = htmlDoc.DocumentNode.SelectSingleNode("//span[@class='product-price']").InnerText.Trim();

            if (decimal.TryParse(priceString, NumberStyles.Currency, CultureInfo.InvariantCulture, out var price))
            {
                return new ProductInfo
                {
                    Title = title,
                    Price = price,
                    Date = DateTime.UtcNow
                };
            }

            throw new Exception("Unable to parse price");
        }
    }
}

يحول هذا المقتطف HtmlAgilityPack إلى عصا سحرية للعطلات.

1.5 إنشاء وحدة تحكم API

لنقم بإنشاء وحدة تحكم تعمل كحارس البوابة لبياناتنا:

using Microsoft.AspNetCore.Mvc;
using PriceMonitorApi.Models;
using PriceMonitorApi.Services;

namespace PriceMonitorApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ScraperController : ControllerBase
    {
        private readonly ScraperService _scraperService;

        public ScraperController(ScraperService scraperService)
        {
            _scraperService = scraperService;
        }

        [HttpGet("productinfo")]
        public async Task<ActionResult<ProductInfo>> GetProductInfo(string url)
        {
            try
            {
                var productInfo = await _scraperService.ScrapeProductInfoAsync(url);
                return Ok(productInfo);
            }
            catch (Exception ex)
            {
                return BadRequest(new { Message = ex.Message });
            }
        }
    }
}

تضمن وحدة التحكم لدينا تسليم البيانات بشكل أسرع من تسليم سانتا أسفل المدخنة.

1.6 تسجيل الخدمات في حاوية DI

وأخيرًا، قم بتسجيل ScraperService للتأكد من أنه متاح عند الحاجة.

services.AddHttpClient<ScraperService>();

الآن أصبحت واجهة برمجة التطبيقات الخاصة بك جاهزة - مثل مزلقة سانتا عشية عيد الميلاد!


الخطوة الثانية: إنشاء تطبيق Blazor

يساعدنا Blazor في تزيين مشروعنا مثل شجرة عيد الميلاد، مما يجعله جذابًا بصريًا وتفاعليًا.

2.1 إعداد مشروع Blazor

بعد ذلك، سنقوم بإنشاء مشروع Blazor الذي يعمل كواجهة لركوب مزلقة لمراقبة الأسعار.

dotnet new blazor -o PriceMonitorBlazor
cd PriceMonitorBlazor

يُضفي هذا الأمر بعض سحر العطلة لإعداد مشروع Blazor WebAssembly الأساسي.

2.2 إضافة النماذج

تمامًا مثل إعداد الزخارف، قم بإضافة نموذج ProductInfo في مشروع Blazor:

namespace PriceMonitorBlazor.Models
{
    public class ProductInfo
    {
        public string Title { get; set; }
        public decimal Price { get; set; }
        public DateTime Date { get; set; }
    }
}

2.3 إنشاء الخدمة لاستدعاءات API

قم بإنشاء خدمة لجلب البيانات من واجهة برمجة التطبيقات (API) الخاصة بنا - فكر في الأمر على أنه صديقنا للتسوق عبر الإنترنت:

using PriceMonitorBlazor.Models;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace PriceMonitorBlazor.Services
{
    public class ScraperService
    {
        private readonly HttpClient _httpClient;

        public ScraperService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ProductInfo> GetProductInfoAsync(string url)
        {
            var response = await _httpClient.GetFromJsonAsync<ProductInfo>($"https://localhost:5001/api/scraper/productinfo?url={url}");
            return response;
        }
    }
}

2.4 تسجيل الخدمات في حاوية DI

تأكد من تسجيل ScraperService حتى نتمكن من إدخاله في مكوناتنا.

builder.Services.AddScoped<ScraperService>();

2.5 إنشاء مكون Blazor

قم بتحديث Pages/Index.razor ليشمل واجهة ممتعة وتفاعلية:

@page "/"
@using PriceMonitorBlazor.Models
@using PriceMonitorBlazor.Services
@inject ScraperService ScraperService

<h3>Price Monitor</h3>

<p>
    <label for="urlInput">Product URL:</label>
    <input id="urlInput" @bind="productUrl" />
    <button @onclick="FetchProductInfo">Fetch Price</button>
</p>

@if (productInfos.Any())
{
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Price</th>
                <th>Date</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in productInfos)
            {
                <tr>
                    <td>@product.Title</td>
                    <td>@product.Price</td>
                    <td>@product.Date</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private string productUrl;
    private List<ProductInfo> productInfos = new List<ProductInfo>();

    private async Task FetchProductInfo()
    {
        if (!string.IsNullOrEmpty(productUrl))
        {
            var productInfo = await ScraperService.GetProductInfoAsync(productUrl);
            productInfos.Add(productInfo);
        }
    }
}

إنه مثل إعداد عرض احتفالي للعطلات — مما يجعل تطبيقنا تفاعليًا ومبهجًا.


الخطوة 3: توصيل كل شيء باستخدام Docker-Compose

الآن دعونا نربط كل شيء باستخدام Docker-Compose، لنحول مشروعنا إلى رحلة مزلقة جيدة التزييت.

3.1 إنشاء ملفات Dockerfiles

قم بإنشاء Dockerfiles لكل من مشاريع API وBlazor:

PriceMonitorApi/Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["PriceMonitorApi/PriceMonitorApi.csproj", "PriceMonitorApi/"]
RUN dotnet restore "PriceMonitorApi/PriceMonitorApi.csproj"
COPY . .
WORKDIR "/src/PriceMonitorApi"
RUN dotnet build "PriceMonitorApi.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "PriceMonitorApi.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PriceMonitorApi.dll"]

PriceMonitorBlazor/Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["PriceMonitorBlazor/PriceMonitorBlazor.csproj", "PriceMonitorBlazor/"]
RUN dotnet restore "PriceMonitorBlazor/PriceMonitorBlazor.csproj"
COPY . .
WORKDIR "/src/PriceMonitorBlazor"
RUN dotnet build "PriceMonitorBlazor.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "PriceMonitorBlazor.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PriceMonitorBlazor.dll"]

3.2 إنشاء docker-compose.yml

قم بتوصيل جميع النقاط (أضواء عيد الميلاد) بملف docker-compose.yml واحد:

version: '3.4'

services:
  api:
    image: pricemonitorapi
    build:
      context: ./PriceMonitorApi
      dockerfile: Dockerfile
    ports:
      - "5000:80"
    networks:
      - price-monitor-network

  blazor:
    image: pricemonitorblazor
    build:
      context: ./PriceMonitorBlazor
      dockerfile: Dockerfile
    ports:
      - "5001:80"
    depends_on:
      - api
    networks:
      - price-monitor-network

networks:
  price-monitor-network:
    driver: bridge

المخططات

فيما يلي الرسوم البيانية للمساعدة في تصور المكونات المختلفة وتدفق البيانات، وهذا سيساعدنا على فهم ما يحدث بالفعل في عالم سانتا لدينا:

مخطط معمارية النظام

الصورة

مخطط تدفق البيانات

Imgur

مخطط التسلسل

الصورة

مخطط المكونات لإعداد Docker

الصورة

الخطوة 4: إدارة URL، والتحديث، والتحديث الدوري الآليفي هذا العرض التوضيحي، سنقوم فقط بتخزينه في الذاكرة، ولكن في التطبيق الحقيقي، يمكنك استخدام قاعدة بيانات. بالنسبة لهذا المشروع الممتع، دعونا نلتزم بالتخزين في الذاكرة.

4.1 تعديل الخدمة لإدارة عناوين URL

أولاً، سوف نتأكد من أن خدمتنا يمكنها التعامل مع إضافة عناوين URL واستردادها، بالإضافة إلى جلب معلومات المنتج:

تحديث Services/ScraperService.cs:

using PriceMonitorBlazor.Models;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace PriceMonitorBlazor.Services
{
    public class ScraperService
    {
        private readonly HttpClient _httpClient;
        private List<string> urls = new List<string>();

        public ScraperService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ProductInfo> GetProductInfoAsync(string url)
        {
            var response = await _httpClient.GetFromJsonAsync<ProductInfo>($"http://localhost:5000/api/scraper/productinfo?url={url}");
            return response;
        }

        public void AddUrl(string url)
        {
            if (!urls.Contains(url))
            {
                urls.Add(url);
            }
        }

        public List<string> GetUrls()
        {
            return urls;
        }

        public void ClearUrls()
        {
            urls.Clear();
        }
    }
}

4.2 تحديث مكون Blazor لإدارة URL وتحديثه

بعد ذلك، قم بتحديث Pages/Index.razor لإضافة إدارة URL والتحديث والتحديث الدوري التلقائي:

@page "/"
@using PriceMonitorBlazor.Models
@using PriceMonitorBlazor.Services
@inject ScraperService ScraperService

<h3>Price Monitor</h3>

<p>
    <label for="urlInput">Product URL:</label>
    <input id="urlInput" @bind="productUrl" />
    <button @onclick="AddUrl">Add URL</button>
</p>

<p>
    <button @onclick="RefreshPrices">Refresh All Prices</button>
</p>

@if (productInfos.Any())
{
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Price</th>
                <th>Date</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in productInfos)
            {
                <tr>
                    <td>@product.Title</td>
                    <td>@product.Price</td>
                    <td>@product.Date</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private string productUrl;
    private List<ProductInfo> productInfos = new List<ProductInfo>();
    private Timer _timer;

    protected override void OnInitialized()
    {
        StartTimer();
    }

    private void StartTimer()
    {
        // Set up the timer to call RefreshPrices every minute (60000 ms)
        _timer = new Timer(async _ => await InvokeAsync(RefreshPrices), null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
    }

    private void AddUrl()
    {
        if (!string.IsNullOrEmpty(productUrl))
        {
            ScraperService.AddUrl(productUrl);
            productUrl = string.Empty;
        }
    }

    private async Task RefreshPrices()
    {
        productInfos.Clear();
        var urls = ScraperService.GetUrls();
        
        foreach (var url in urls)
        {
            var productInfo = await ScraperService.GetProductInfoAsync(url);
            productInfos.Add(productInfo);
        }
        StateHasChanged();
    }
}

في هذا المكون المحدث:

  • نستخدم Timer للاتصال بطريقة RefreshPrices بشكل دوري كل دقيقة.
  • تعمل طريقة StartTimer على تهيئة المؤقت ليبدأ على الفور ثم يتم تشغيله كل 60 ثانية.
  • يستدعي أسلوب دورة الحياة OnInitialized StartTimer عند تهيئة المكون لبدء التحديث الدوري.

4.3 تشغيل الحل

لتشغيل تطبيق Blazor المحدث بالميزات الجديدة، قم بإعادة بناء حاويات Docker وإعادة تشغيلها:

docker-compose build
docker-compose up

بعد تحميل http://localhost:5001 في متصفحك، يجب الآن على تطبيق Blazor تحديث أسعار المنتجات تلقائيًا كل دقيقة بالإضافة إلى السماح بالتحديثات اليدوية وإدارة عناوين URL.

الخلاصة

لقد كان بناء نظام مراقبة الأسعار هذا بمثابة انفجار احتفالي! لم ينقذني ذلك من ضغوط التحقق اليومي من الأسعار فحسب، بل أظهر أيضًا سحر تقنيات الويب الحديثة.

التقويم التقني الاحتفالي 2024

لقد أنشأت هذا المنشور كجزء من حدث Festive Tech Calendar 2024، الذي يجمع عشاق التكنولوجيا والمبتكرين والحالمين الرقميين لمشاركة المعرفة والاحتفال بدمج الروح الاحتفالية والعجائب التكنولوجية. لا تقتصر هذه المبادرة على التعلم والتواصل فحسب، بل تتعلق أيضًا بتقديم العطاء.

يدعم تقويم التكنولوجيا الاحتفالي 2024 مؤسسة Beatson Cancer الخيرية هذا العام. جمعية بيتسون لمكافحة السرطان مكرسة لدعم الأشخاص المصابين بالسرطان وأسرهم ومتخصصي الرعاية الصحية الذين يعتنون بهم. يمكن العثور على مزيد من المعلومات حول عملهم المذهل على https://www.beatsoncancercharity.org/.

قم بزيارة موقع ويب Festive Tech Calendar على https://festivetechcalendar.com للحصول على الأسئلة الشائعة ومزيد من التفاصيل حول الحدث.

هو هو هو!

لقد كان إنشاء هذا المشروع طريقة رائعة للمساهمة في مجتمع التكنولوجيا الاحتفالي ودعم قضية عظيمة في نفس الوقت.

أتمنى أن تجد هذا الدليل مفيدًا وأن يلهمك لاستكشاف المزيد من الطرق لاستخدام التكنولوجيا لتبسيط المهام اليومية.

إذا كانت لديك أي أسئلة أو كنت بحاجة إلى مزيد من المساعدة، فلا تتردد في التواصل معنا.

ترميز سعيد!