<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های میثاق لطفی</title>
        <link>https://virgool.io/feed/@misaghlb</link>
        <description>برنامه نویس بک اند، علاقه مند به علم و یادگیری چیزها</description>
        <language>fa</language>
        <pubDate>2026-06-11 21:02:30</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/2006/avatar/qwLpDg.png?height=120&amp;width=120</url>
            <title>میثاق لطفی</title>
            <link>https://virgool.io/@misaghlb</link>
        </image>

                    <item>
                <title>ایجاد لینک موقت دانلود در جنگو</title>
                <link>https://virgool.io/@misaghlb/%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-%D9%84%DB%8C%D9%86%DA%A9-%D9%85%D9%88%D9%82%D8%AA-%D8%AF%D8%A7%D9%86%D9%84%D9%88%D8%AF-%D8%AF%D8%B1-%D8%AC%D9%86%DA%AF%D9%88-s0igml9szxf6</link>
                <description>در پست قبل با استفاده از وب سرور Nginx توانستیم دانلود مستقیم فایل ها را محدود کنیم و توسط جنگو کنترل کنیم که چه کسی بتواند فایل را دانلود کند. اما اگر فایل های ما حجم بالایی داشته باشند و نیاز به استفاده از سرور دیگری برای ذخیره فایلها داشته باشیم چگونه باید از دانلود مستقیم فایلها جلوگیری کنیم؟ در این پست به این موضوع میپردازیم.در اینجا من از هاست دانلود استفاده میکنم که هزینه مناسبی برای ذخیره سازی فایلها دارد و حجم بالایی را هم در اختیار میگذارد و اکثر این هاست ها اسکریپت PHP را پشتیبانی میکنند بنابراین میتوانیم دانلود فایلها را کنترل و محدود کنیم.ابتدا در جنگو مدلی میسازیم که بتوانیم مشخصات فایل و آدرس ذخیره آن در هاست را ذخیره کنیم و همچنین همینطور که در مدل من میبینید میتوانید گروه هایی را هم که اجازه دسترسی به این فایل را دارند به مدل اضافه کنید.class Video(BaseModel):
    title = models.CharField(max_length=100, null=True, blank=True)
    address = models.CharField(max_length=255, null=True, blank=True)
    allowed_memberships = models.ManyToManyField(Group)

    def __str__(self):
        return self.titleحالا باید بتوانیم لینک موقتی تولید کنیم که محدودیت هایی برای دانلود و کلاینت دانلود کننده داشته باشد. یکی از محدودیت هایی که اینجا اعمال میکنیم محدود کردن زمان فعال بودن لینک دانلود هست و دیگری اینکه لینک فقط برای آیپی درخواست کننده فعال باشد و در نتیجه کابر نتواند لینک را به شخص دیگری با آیپی متفاوتی دهد.def visitor_ip_address(request):
    x_forwarded_for = request.META.get(&#039;HTTP_X_FORWARDED_FOR&#039;)
    if x_forwarded_for:
        ip = x_forwarded_for.split(&#039;,&#039;)[0]
    else:
        ip = request.META.get(&#039;REMOTE_ADDR&#039;)
    return ip

def hash_md5(request, linkFile, valid_time, is_timestamp=True):
    path = linkFile
    secret = env(&#039;HASH_SECRET&#039;)
    ip = visitor_ip_address(request)
    if is_timestamp:
        t = int(round(time.time() + valid_time))
    else:
        t = valid_time
    hashed = hashlib.md5()
    hashed.update(ip.encode(&#039;UTF-8&#039;))
    hashed.update(secret.encode(&#039;UTF-8&#039;))
    hashed.update(path.encode(&#039;UTF-8&#039;))
    hashed.update(str(t).encode(&#039;UTF-8&#039;))
    return hashed.hexdigest(), t, pathخب در کد بالا دوتا فانکشن داریم که اولی برای بدست آوردن آیپی کلاینت است و دومی که فرآیند هش کردن این متغیرهای محدود کننده را انجام میدهد که در نهایت هش تولید شده را میتوانیم به هاست دانلود بفرستیم و در آنجا اعتبار لینک را چک کنیم.حالا سریالایزری میسازیم تا زمانی که کاربر درخواست دسترسی به فایل را داشت بتوانیم جوابی را که شامل اطلاعات فایل و لینک موقت است را به کاربر بدهیم:class VideoSerializer(ModelSerializer):
    dl_link = SerializerMethodField()
    class Meta:
        model = Video
        fields = (&#039;id&#039;, &#039;address&#039;, &#039;title&#039;, &#039;dl_link&#039;,)

    def get_dl_link(self, obj: Video) -&gt; str:
        request = self.context.get(&amp;quotrequest&amp;quot)
        if not request:
            return &amp;quot&amp;quot
        hashed, t, path = hash_md5(request, obj.address, 3600)
        return f&amp;quot{settings.DOWNLOAD_HOST_DOMAIN}/downloads?path={path}&amp;hash={hashed}×tamp={t}&amp;quotدر اینجا لینک موقت را برای کاربر تولید میکنیم که محدودیت زمانی را هم ۳۶۰۰ ثانیه در نظر گرفتیم.یک ویو هم میسازیم که کاربر بتونه درخواست دانلود فایل را بدهد:class GetDLLink(APIView):
    permission_classes = (AllowAny,)
    def get(self, request, pk):
        video = Video.objects.get(id=pk)
        ser = VideoSerializer(video, context={&#039;request&#039;: request})
        return Response(ser.data)در اینجا میتوانید قبل از اینکه لینکی به کاربر بدهید، اگر بررسی ها و محدودیت های دیگری هم نیاز است را انجام دهید، مثلا محدود به کاربر ثبت نام شده با شرط عضویت در فلان گروه باشد.اینجا کار ما با جنگو تمام میشه و کاربر لینک موقتی داره که میتواند به هاست دانلود درخواست دانلود فایل را بدهد.حالا در هاست دانلود برای اینکه مسیر مستقیم فایلها قابل حدس نباشه، میتوانیم فایل ها را در دایرکتوری هایی با نام غیرقابل حدس بزاریم چون کاربر آدرس ذخیره فایلها را نمیبیند، همچنین با فایل htaccess هم روی دسترسی به فایلها محدودیت بگذارید تا امنیت کار بالاتر برود.برای اینکه سورس فایل PHP مقداری طولانی هست میتوانید از این لینک به آن دسترسی داشته باشید. در این فایل پارامترهای لینک موقت ارسال شده را میگیریم و چک میکنیم که لینک معتبر باشد، اگر معتبر بود از مسیر دایرکتوری هایی که فایلها در آن قرار دارند، فایل را برای کاربر قابل دانلود میکنیم.کار تمام است و حالا محل ذخیره سازی فایلها در سرور دیگری است و فشار زیادی به سرور اصلی وارد نمیشود و همچنین دسترسی به فایلها هم امن و محدود است.امیدوارم مفید بوده باشه.</description>
                <category>میثاق لطفی</category>
                <author>میثاق لطفی</author>
                <pubDate>Mon, 24 Jan 2022 13:41:57 +0330</pubDate>
            </item>
                    <item>
                <title>امنیت فایلها در جنگو</title>
                <link>https://virgool.io/@misaghlb/%D8%A7%D9%85%D9%86%DB%8C%D8%AA-%D9%81%D8%A7%DB%8C%D9%84%D9%87%D8%A7-%D8%AF%D8%B1-%D8%AC%D9%86%DA%AF%D9%88-qtfiddazo25x</link>
                <description>ملک خصوصی - بدون اجازه وارد نشوید!سلام توی این پست میخوام درباره امن نگه داشتن فایلها توی جنگو بنویسم و ببینیم چطور میشه روی دانلود فایلها کنترل داشت.فرض کنید سایتی داریم که فایلها قراره فروشی باشن و در نتیجه هرکس مبلغ اون فایل رو پرداخت کرد بتونه به اون دسترسی داشته باشه. در حالت عادی که فایلها رو در دیسک ذخیره میکنیم و با وب سرور Nginx سرو میکنیم همه افراد میتونن با لینک مستقیم به اونا دسترسی داشته باشن و دانلود بکنن بدون اینکه جنگو مدیریت و کنترلی روی اونا داشته باشه ولی این چیزی نیست که اینجا بدرد ما بخوره.راه حل چیه؟ راه حلش اینه که به طریقی به وب سرور بگیم که این فایلها رو مستقیم سرو نکن و اول برو از اپلیکشن اجازه بگیر و اگر کاربر مجاز بود اونوقت براش سرو کن. به این صورت جنگو ابتدا چک میکنه که کاربر اجازه دسترسی به فایلی که وب سرور میخواد سرو کنه را داره یا نه . اگر داشت ادامه کار سرو کردن رو میسپاره به Nginx که خیلی خوب از پس اینکارها برمیاد.واسه اینکار میریم توی فایل کانفیک Nginx و محلی که فایلهای خصوصی در اون قرار دارند رو به اینصورت تعریف میکنیم:location /protected/ {
  internal;
  alias/protected_files;
}اینجا میسر فایلهای خصوصی رو مشخص کردیم. در ادامه باید مسیری را در جنگو تعریف بکنیم که مسئول بررسی کردن و اجازه دادن هست و مثل یک دروازه ورودی عمل میکنه.در اینجا برای تعریف دروازه ورودی یک View در جنگو درست میکنیم به اینصورت:@login_required
def download(request, file_id):
    myFile = VideoModel.objects.get(id=file_id)
    if not request.user.is_authenticated():
        return render(request, &amp;quotindex.html&amp;quot)
    response = HttpResponse()
    response[&amp;quotContent-Disposition&amp;quot] = &amp;quotattachment; filename={0}&amp;quot.format(
            myFile.file.name)
    response[&#039;X-Accel-Redirect&#039;] = &amp;quot/protected/{0}&amp;quot.format(myFile.file.name)
    return responseاینجا هرگونه بررسی لازم باشه انجام میدیم و در نهایت با هدر هایی که قرار میدیم به وب سرور (در اینجا Nginx) میگیم که ریدایرکت کن و برو این فایل رو سرو کن بدون اینکه آدرس Url کاربر تغییر کنه. در واقع وب سرور با دیدن این هدر (X-Accel-Redirect) متوجه میشه که باید ریدایرکت داخلی بزنه به یه مسیر دیگه.حالا کافیه بجای اینکه لینک مستقیم بدیم،کاربرها رو با آیدی فایل موردنظر هدایت کنیم به این ویو که ساختیم و اگر معتبر بود دانلود میشه به همین باحالی :)https://example.com/download/45امیدوارم که مفید بوده باشه اگر نظری پیشنهادی دارید خوشحال میشم بدونم و باهاتون در ارتباط باشم.</description>
                <category>میثاق لطفی</category>
                <author>میثاق لطفی</author>
                <pubDate>Sun, 26 Apr 2020 11:37:14 +0430</pubDate>
            </item>
                    <item>
                <title>معرفی فریمورک Nest.Js</title>
                <link>https://virgool.io/@misaghlb/%D9%85%D8%B9%D8%B1%D9%81%DB%8C-%D9%81%D8%B1%DB%8C%D9%85%D9%88%D8%B1%DA%A9-nestjs-mipqcp2mcrhv</link>
                <description>در این مطلب مقداری درباره فریمورکی که تازه شروع کردم به کار کردن صحبت می کنم و میریم که یک نگاه کلی به این فریمورک داشته باشیم و بیشتر باهاش آشنا بشیم.نست فریمورکی برای ساختن اپلیکشن های بک اند در NodeJs هست که با TypeScript ساخته شده و بطور کامل ازش پشتیبانی میکنه و همچنین قابلیت های کار با وب سوکت، GraphQl ،Rest و ... رو برامون فراهم میکنه.در لایه های زیرین خودش از فریمورک ExpressJs بطور پیشفرض استفاده میکنه و همچنین میتونید بجای اکسپرس از Fastify هم استفاده کنید. در واقع یک لایه ابسترکشن روی فریمورکهای express یا fastify هست و در عین حال امکان این رو بهمون میده که مستقیما با api های این فریمورک ها هم کار کنیم و خوبیش اینه که میتونیم از لایبری ها و ماژول هایی که برای این فریمورک ها هست به راحتی استفاده کنیم.چرا بجای اکسپرس از نست استفاده کنیم؟یکی از دلایل میشه گفت وجود نداشتن ساختار و استانداردی در پروژه های nodejs هست. هر موقع که وارد یک پروژه جدید نود میشیم احتمال زیاد با یک ساختار متفاوت رو به رو میشیم مثلا بعضی ها ممکنه فقط از یک فایل استفاده کرده باشند ! یا بعضیا از معماری MVC و خیلی چیزای دیگه. این حالت بیشتر واسه کسایی که تازه کار هستن و زیاد با معماری ها و ساختارها آشنا نیستن میتونه گیج کننده باشه و در کل احتمال تولید کد کثیف و غیرقابل نگهداری با گذشت زمان بیشتر میشه که در نهایت کار توسعه رو سخت تر و زمانبر میکنه.نست فقط یک ساختار و معماری رو بهمون معرفی میکنه و میگه که فقط توی این قالب و استایل کد بزنین.کامپوننت ها معماری یک اپلیکشن نست به این صورته:  کنترلر ها (Controllers)ماژول ها (Modules)پروایدر Provider ها (services, repositories, factories, helpers , ...)کلاس های DTOکلاس های مدل (Entities)دلیل اصلی استفاده از این معماری و ساختار این هست که اپلیکشن رو به بخش های کوچیکی بشکنیم. یادگیری و کارکردن به این شیوه برای کسایی که انگولار یا spring boot کار کردن میتونه ساده باشه.در ادامه به معرفی این کامپوننت ها بصورت اجمالی میپردازیم.کنترلر ها (Controllers):هدف اصلی دریافت درخواست از کاربر یا هرچیزی دیگه ای هست و فرستادنش به کامپوننت سرویس ها (business logic) برای انجام کار مورد نظر و سپس برگرداندن پاسخ.ماژول ها (Modules):ماژول مثل یک کانتینر هست که بقیه بخش هایی که بهم مرتبط هستن رو در خودش نگه میداره .مثلا یک ماژول بنام user فقط شامل سرویس ها و کنترل ها و ... مخصوص و مرتبط کار با یوزر هاست و به این صورت بخش های مختلف اپ رو جداسازی میکنه. پروایدر ها Providers:این بخش مقداری عمومی هستش و میتونه شامل ریپازیتوری مدل های دیتابیس، سرویس، کلاس های helper و ... باشه. ایده کلی پرواید ها اینه که میتونه در جاهای مختلف به صورت وابستگی تزریق بشه، مثل یک کانکشن دیتابیس که میتونه در بخش ها و کلاس های مختلفی تزریق بشه و امکان کار کردن با دیتابیس رو بده. در واقع سرویس ها شامل business logic و منطق برنامه هستن که میتونن در هرجایی استفاده بشن.کلاس های DTO:این بخش که مخفف Data Transfer Object هست یک ساختار و فرمت برای داده هایی که در یک اندپوینت (route) از کاربر دریافت میشه  تعیین میکنه. مثلا میخواید که کاربر سن رو بصورت عدد براتون بفرسته یا نام بصورت یک رشته باشه. که با ترکیب اینها با لایبری هایی مثل class-validator میتونیم بعنوان ولیدشن هایی قوی ازشون استفاده کنیم و داده های ورودی کاربر رو کنترل کنیم.مدل ها (Entities):مدل ها جایی هستن که اطلاعات مربوط به دیتابیس رو توی اون ها میسازیم مثلا با استفاده از typeorm که یک ORM کاملا سازگار با تایپ اسکریپت هست میتونیم یک کلاس مدل بسازیم و جدول دیتابیس رو بدون یک خط کد SQL و فقط با کد تایپ اسکریپت درست کنیم.سخن پایانی:این فریمورک استاداردها و استایلی رو جلوی پامون میزاره که باعث افزایش کارایی کدنویسی میشه و ممکنه که در نگاه اول سخت بنظر بیاد ولی وقتی که یادش گرفتید درکنار تایپ اسکریپت میتونه به سرعت کدنویسی، خوانایی، ساختار منظم، نگهداری کد و ...  کمک زیادی کنه.میتونید به سایت nestjs مراجعه کنید که داکیومنتش خیلی خوب توضیح داده و به راحتی میتونید از اونجا یاد بگیرید.خب دوستان سوالی داشتین میتونید از طریق ایمیل misaghlb@gmail.com با من در ارتباط باشید.خوش باشین</description>
                <category>میثاق لطفی</category>
                <author>میثاق لطفی</author>
                <pubDate>Tue, 11 Feb 2020 22:38:52 +0330</pubDate>
            </item>
                    <item>
                <title>کار با موقعیت مکانی در جنگو</title>
                <link>https://virgool.io/@misaghlb/%DA%A9%D8%A7%D8%B1-%D8%A8%D8%A7-%D9%85%D9%88%D9%82%D8%B9%DB%8C%D8%AA-%D9%85%DA%A9%D8%A7%D9%86%DB%8C-%D8%AF%D8%B1-%D8%AC%D9%86%DA%AF%D9%88-bkpjhj6k4xso</link>
                <description>سلام اول از همه چیز این اولین تجربه بلاگ نویسی توی ویرگول هست که امیدوارم مفید باشه.همونطور که از عنوان مشخص هست میخواهیم مقداری با داده های جغرافیایی در فریمورک جنگو کار کنیم و با یک مثال کوچک یک دید کلی به نحوه کار داشته باشیم.فرض کنید در حال ساخت برنامه ای هستید که یک سری شعبه ها از رستوران در مناطق مختلف شهر وجود دارد و میخواهید نزدیک ترین شعبه ها رو با توجه موقعیت مکانی کاربر بهش نشون بدید. در اینجا با این مثال بصورت خیلی کلی و کوچک پیش میریم تا ببینیم چجور میشه همچین چیزی را پیاده کرد.پیش نیاز هااول از همه چیز نیاز داریم که افزونه را روی دیتابیس پستگرس نصب بکنید تا بتونه با داده های جغرافیایی کار کنه، اسم این افزونه Postgis هست، برای نصب این افزونه کافیه یک سرچ ساده بزنید و نحوه نصب برای سیستم عامل های مختلف را میاره.برای نمونه برای سیستم عامل مک به این شیوه هست:$ brew install gdal
$ brew install libgeoip وقتی که نصب شد باید به دیتابیسی که از قبل برای پروژه جنگو ساختید وصل بشید:psql DATABASE_NAME و با دستور :`CREATE EXTENSION IF NOT EXISTS postgis;`این افزونه را روی دیتابیس فعال کنید.حالا باید جنگو را تنظیم کنیم که امکانات کار با این داده ها را برای ما فراهم کنه. برای اینکار فایل settings.py را باز کنید و در بخش تنظیمات دیتابیس گزینه ENGINE را به:&#039;django.contrib.gis.db.backends.postgis&#039;تغییر بدید.در نهایت باید همچین چیزی بشه:DATABASES = { 
    &#039;default&#039;: { 
        &#039;ENGINE&#039;: &#039;django.contrib.gis.db.backends.postgis&#039;, 
        &#039;NAME&#039;: DATABASE_NAME,
         &#039;USER&#039;: DATABASE_USER,
         &#039;PASSWORD&#039;: DATABASE_PASSWORD,
         &#039;HOST&#039;: DATABASE_HOST,
         &#039;PORT&#039;: &#039;DATABASE_PORT&#039;,
     } 
}همچنین در بخش INSTALLED_APPS باید django.contrib.gis را اضافه کنید:INSTALLED_APPS = [  
   ...  
   &#039;django.contrib.gis&#039;,
   ...
 ]راه اندازی مدل ها به کمک فیلدهای GeoDjangoحالا وقتش رسیده که مدل ها را درست کنیم. همونطور که اول مطلب گفتم سناریو اینه که یکسری شعبه های رستوران در نقاط مختلف شهر وجود دارد که قرار است با توجه به نزدیک بودن به موقعیت مکانی کاربر به ترتیب نشان داده بشوند.from django.contrib.gis.db import models

class Branch(models.Model):
    name = models.CharField(max_length=100)
    location = models.PointField()این مدل شعبه هست که بطور ساده یک نام دارد و یک مختصات مکانی، که نقطه جغرافیایی شعبه رو مشخص میکنه. حالا کافیه مایگرشن را انجام بدین تا توی دیتابیس ساخته بشه.برای ساخت یک نقطه جغرافیایی (PointField) به دو عدد طول (longitude) و عرض (latitude) جغرافیایی نیاز داریم.شیوه ساختن یک نقطه به شکل زیر هست:from django.contrib.gis.geos import Point

latitude = 17.58207
longitude = 141.05398

# ساخت نقطه جغرافیایی با نقاط بالا
point = Point(float(longitude), float(latitude), srid=4326)

# ساخت یک نمونه شعبه جدید
branch = Branch()
branch.location = point
branch.name = &#039;نام شعبه ۱&#039;
branch.save()خب حالا یک شعبه ساختیم که مکانش هم بطور دقیق مشخص کردیم، شما میتونید چندتا بسازید با نقاط مختلف.با این کار خیلی ساده میتونیم فاصله کاربر تا نقطه را مشخص کنیم:from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.geos import Point

# از داخل درخواستی که دریافت شده نقاط را میگیریم:
latitude = request.query_params.get(&#039;latitude&#039;) longitude = request.query_params.get(&#039;longitude&#039;)

# با استفاده از اعداد بالا نقطه جغرافیایی کاربر را میسازیم
point_of_user = Point(float(longitude), float(latitude), srid=4326)

# نزدیکترین شعبه ها به موقعیت کاربر را دریافت میکنیم
branches = Branch.objects.filter().annotate(distance=Distance(&#039;location&#039;, point_of_user)).order_by(&#039;distance&#039;)به همین راحتی تونستیم طبق مکان کاربر شعبه هارو بهش نشون بدیم که اپ را خیلی کاربرپسند تر میکنه. بعنوان نکته پایانی با این کد هم میشه فاصله دو نقطه را در واحد کیلومتر دریافت کرد:روش اول:from geopy.distance import distance as geopy_distance

distance = geopy_distance(POINT1, POINT2).kilometersکد بالا نیازمند نصب پکیج geopy هست که امکانات بیشتری هم در اختیارمون میذاره:pip install geopyروش دوم:بدون نصب پکیج بالا هم به اینصورت میشه:distance = point1.distance(point2)

distance_in_km = distance * 100به همین راحتی و باحالی :) نظرات و پیشنهادات خودتون را با من درمیان بگذارید.میتونید در تلگرام هم دنبال کنید.</description>
                <category>میثاق لطفی</category>
                <author>میثاق لطفی</author>
                <pubDate>Wed, 29 Jan 2020 09:00:33 +0330</pubDate>
            </item>
            </channel>
</rss>