Skip to content

Commit

Permalink
[v1.1.2] Merge pull request #30 from KageRyo/develop
Browse files Browse the repository at this point in the history
Update to RyoURL v1.1.2
  • Loading branch information
KageRyo authored Aug 2, 2024
2 parents 53e8f21 + dc96d9f commit ef0dc07
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 32 deletions.
28 changes: 21 additions & 7 deletions RyoURL/shortURL/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ninja import NinjaAPI, Schema
from ninja.renderers import JSONRenderer
from django.shortcuts import get_object_or_404
from django.utils.crypto import get_random_string
from django.core.serializers.json import DjangoJSONEncoder

from .models import Url
Expand Down Expand Up @@ -40,13 +41,26 @@ class UrlSchema(Schema):
class ErrorSchema(Schema):
message: str

# BASE62 編碼的函式
def base62_encode(num):
base62 = string.digits + string.ascii_letters
if num == 0:
return base62[0]
array = []
while num:
num, rem = divmod(num, 62)
array.append(base62[rem])
array.reverse()
return ''.join(array)

# 產生隨機短網址的函式
def generator_short_url(length = 6):
char = string.ascii_letters + string.digits
while True:
short_url = ''.join(random.choices(char, k=length))
if not Url.objects.filter(short_url=short_url).exists():
return short_url # 如果短網址不存在 DB 中,則回傳此短網址
def generator_short_url(orign_url: str, length = 6):
hash_value = abs(hash(orign_url)) # 取得原網址的 hash 值
encode = base62_encode(hash_value) # 將 hash 值轉換為 BASE62 編碼
if len(encode) < length:
encode += get_random_string(length - len(encode), string.ascii_letters + string.digits)
return encode
return encode[:length]

# 處理短網址域名的函式
def handle_domain(request, short_string):
Expand All @@ -71,7 +85,7 @@ def index(request):
# POST : 新增短網址 API /short_url
@api.post("short-url", response={200: UrlSchema, 404: ErrorSchema})
def create_short_url(request, orign_url: HttpUrl, expire_date: Optional[datetime.datetime] = None):
short_string = generator_short_url()
short_string = generator_short_url(orign_url)
short_url = HttpUrl(handle_domain(request, short_string))
url = create_url_entry(orign_url, short_string, short_url, expire_date)
return 200, url
Expand Down
67 changes: 42 additions & 25 deletions RyoURL/shortURL/views.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import logging

from redis import RedisError
from django.core.cache import cache
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.http import HttpResponse, HttpResponseRedirect
from django.db.models import F
from typing import Optional

from .models import Url

# logging 的設定
logger = logging.getLogger(__name__)

# 常數設定
CACHE_TIMEOUT = 60 * 60 * 24 # 快取過期時間為 24 小時
VISIT_COUNT_UPDATE_THRESHOLD = 10 # 訪問次數更新的閾值

# 檢查短網址是否過期的函式
def is_url_expired(url):
def is_url_expired(url: Url) -> bool:
if url.expire_date and url.expire_date < timezone.now():
try:
url.delete()
Expand All @@ -20,37 +28,46 @@ def is_url_expired(url):
return False

# 更新資料庫中的訪問次數的函式
def update_visit_count(visit_count, url):
def update_visit_count(visit_count: int, url: Url) -> None:
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}')
def handle_visit_count(url: Url) -> None:
try:
# 處理快取
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=CACHE_TIMEOUT) # 初始化快取,設定快取時間為 24 小時
visit_count = cache.incr(cache_key) # 訪問次數加 1
logger.debug(f'目前快取中的訪問次數: {visit_count}')

# 每 10 次訪問更新資料庫
if visit_count % VISIT_COUNT_UPDATE_THRESHOLD == 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=CACHE_TIMEOUT) # 快取 24 小時過期
Url.objects.filter(id=url.id).update(visit_count=F('visit_count') + (visit_count % VISIT_COUNT_UPDATE_THRESHOLD))
logger.debug(f'每日訪問次數儲存進資料庫: {visit_count}')

# 每 10 次訪問更新資料庫
if visit_count % 10 == 0:
update_visit_count(visit_count, url)
# 如果 Redis 連線失敗,直接更新資料庫
except RedisError as e:
logger.error(f'與 Redis 操作失敗,直接更新資料庫: {e}', exc_info=True)
update_visit_count(1, url)
# 其他錯誤
except Exception as e:
logger.error(f'處理訪問次數時發生錯誤: {e}', exc_info=True)

# 處理快取過期的處理(每日至少儲存至資料庫一次)
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):
def redirectShortUrl(request, short_string: str) -> HttpResponse:
try:
url = get_object_or_404(Url, short_string=short_string)
if is_url_expired(url): # 檢查短網址是否過期
Expand Down

0 comments on commit ef0dc07

Please sign in to comment.