diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3048a2b..d4a7f36 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,8 +9,9 @@ RUN apt-get update && apt-get install -y \ git \ vim \ curl \ - wget + wget \ + redis-server COPY . . -CMD ["bash"] \ No newline at end of file +CMD service redis-server start && python manage.py runserver 0.0.0.0:8000 \ No newline at end of file diff --git a/RyoURL/RyoURL/settings.py b/RyoURL/RyoURL/settings.py index 5c537ef..b2482f7 100644 --- a/RyoURL/RyoURL/settings.py +++ b/RyoURL/RyoURL/settings.py @@ -18,7 +18,6 @@ # 建立專案的根目錄路徑 BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # 快速開發設定 - 不適用於生產環境 # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ @@ -97,6 +96,29 @@ } } +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379/1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + } + } +} + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + }, + }, + 'root': { + 'handlers': ['console'], + 'level': 'DEBUG', + }, +} # Password validation # 密碼驗證 diff --git a/RyoURL/shortURL/api.py b/RyoURL/shortURL/api.py index c42aa9d..0f66f57 100644 --- a/RyoURL/shortURL/api.py +++ b/RyoURL/shortURL/api.py @@ -1,13 +1,11 @@ import random import string import datetime -import requests from typing import List, Optional from pydantic import HttpUrl, AnyUrl from ninja import NinjaAPI, Schema -from ninja.responses import Response from ninja.renderers import JSONRenderer from django.shortcuts import get_object_or_404 from django.core.serializers.json import DjangoJSONEncoder diff --git a/RyoURL/shortURL/views.py b/RyoURL/shortURL/views.py index e80b8e7..8313f54 100644 --- a/RyoURL/shortURL/views.py +++ b/RyoURL/shortURL/views.py @@ -1,20 +1,68 @@ -from django.shortcuts import get_object_or_404, redirect +import logging +from django.core.cache import cache +from django.shortcuts import get_object_or_404 from django.utils import timezone -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponsePermanentRedirect +from django.db.models import F from .models import Url -# 將短網址導向原網址的函式 -def redirectShortUrl(request, short_string): - url = get_object_or_404(Url, short_string=short_string) - - # 檢查短網址是否已過期 +# logging 的設定 +logger = logging.getLogger(__name__) + +# 檢查短網址是否過期的函式 +def is_url_expired(url): if url.expire_date and url.expire_date < timezone.now(): - url.delete() - return HttpResponse("此短網址已過期並已被刪除。", status=404) + try: + url.delete() + return True + except Exception as e: + logger.error(f'刪除過期URL時發生錯誤: {e}') + return False + +# 更新資料庫中的訪問次數的函式 +def update_visit_count(visit_count, url): + Url.objects.filter(id=url.id).update(visit_count=F('visit_count') + visit_count) + logger.debug(f'訪問次數儲存進資料庫: {visit_count}') + +# 處理訪問次數與快取的函式 +def handle_visit_count(url): + # 處理快取 + cache_key = f'visit_count_{url.id}' # 設定快取的鍵 + visit_count = cache.get(cache_key) # 從快取中取得訪問次數 + if visit_count is None: # 如果快取中沒有訪問次數,那就從資料庫拿 + visit_count = url.visit_count + logger.debug(f'在快取中找不到訪問次數,從資料庫拿: {visit_count}') + + # 增加訪問次數 + cache.set(cache_key, visit_count, timeout=60*60*24) # 初始化快取,設定快取時間為 24 小時 + visit_count = cache.incr(cache_key) # 訪問次數加 1 + logger.debug(f'目前快取中的訪問次數: {visit_count}') - # 更新訪問次數 - url.visit_count += 1 - url.save() + # 每 10 次訪問更新資料庫 + if visit_count % 10 == 0: + update_visit_count(visit_count, url) + + # 處理快取過期的處理(每日至少儲存至資料庫一次) + daily_update_key = f'daily_update_{url.id}' + if not cache.get(daily_update_key): + cache.set(daily_update_key, True, timeout=60*60*24) # 快取 24 小時過期 + Url.objects.filter(id=url.id).update(visit_count=F('visit_count') + (visit_count % 10)) + logger.debug(f'每日訪問次數儲存進資料庫: {visit_count}') + +# 將短網址導向原網址的函式 +def redirectShortUrl(request, short_string): + try: + url = get_object_or_404(Url, short_string=short_string) + if is_url_expired(url): # 檢查短網址是否過期 + return HttpResponse("此短網址已過期並已被刪除。", status=410) # 410 Gone + handle_visit_count(url) # 處理訪問次數 + + # 將使用者重新導向至原網址 + return HttpResponsePermanentRedirect(url.orign_url) - # 進行重定向 - return redirect(url.orign_url) \ No newline at end of file + except Url.DoesNotExist: + logger.warning(f'短網址不存在: {short_string}') + return HttpResponse("此短網址不存在。", status=404) + except Exception as e: + logger.error(f'發生錯誤: {e}', exc_info=True) + return HttpResponse("發生錯誤,請稍後再試。", status=500) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 6a340f9..8b3a0c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ Django==4.2 django-ninja django-cors-headers +django-redis # 工具 python-dotenv