1. 主要的库:
- Django == 3.2.9
- djangorestframework == 3.12.4
- django-filter == 21.1
- djangorestframework-filters == 1.0.0.dev2
- django-extensions == 3.1.5
- django-rest-framework-rules == 1.0.0
- mysqlclient == 2.0.3
2. 实现思路:
- 通过mysql数据库,建立原链接和短链接的关系并存储起来;
- 通过进制转换,把10进制转为62进制,能大大缩短字符串的长度;
- 用户访问短链接的时候转发到原来的链接。
3. 进制转换(10进制转62进制):
把数据表自增的id转换成62进制,这样即使数据很多,短链接的长度也能不会太长,下面直接上代码:
# -*- coding: utf-8 -*-
__author__ = 'JayChen'
BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def encode(num: int, alphabet: str = BASE62) -> str:
"""
10进制转62进制
Args:
num: 10进制的整型数字
alphabet: 字符串
Return:
62进制字符串
"""
if num == 0:
return alphabet[0]
arr = []
base = len(alphabet)
while num:
num, rem = divmod(num, base)
arr.append(alphabet[rem])
arr.reverse()
return ''.join(arr)
def decode(string: str, alphabet: str = BASE62) -> int:
"""
62进制转10进制
Args:
string: 62进制的字符串
alphabet: 字符串
Return:
10进制数字
"""
base = len(alphabet)
str_len = len(string)
num = 0
idx = 0
for char in string:
power = (str_len - (idx + 1))
num += alphabet.index(char) * (base ** power)
idx += 1
return num
4. 创建数据库表:
# -*- coding: utf-8 -*-
"""短链接表"""
from django.db import models
from django.conf import settings
from django_extensions.db.fields import CreationDateTimeField
from django.utils.translation import gettext as _
__author__ = 'JayChen'
class ShortUrl(models.Model):
# Translators: 原始链接
original_url = models.CharField(_('Original url'), max_length=255, blank=True, null=True, db_index=True)
# Translators: 短链接
short_url = models.CharField(_('Short url'), max_length=255, blank=True, null=True, db_index=True)
# Translators: 有效截止时间
deadline = models.DateTimeField(_('Deadline'), blank=True, null=True, db_index=True)
# Translators: 创建时间
created_on = CreationDateTimeField(_('Created on'), db_index=True)
# Translators: 创建人
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='%(app_label)s_%(class)s_created_by',
blank=True, null=True, on_delete=models.SET_NULL)
class Meta:
ordering = ['-id']
app_label = 'app'
db_table = 'app_short_url'
def __str__(self):
return self.short_url
@classmethod
def resource(cls):
return 'short-url'
@property
def display(self):
return {
'long_url': self.original_url,
'short_url': self.short_url
}
@property
def resource_display(self):
return {
'long_url': self.original_url,
'short_url': self.short_url,
'deadline': self.deadline,
'created_on': self.deadline
}
@classmethod
def model_name(cls):
return _('ShortUrl')
5. 下面是序列化部分:
# -*- coding: utf-8 -*-
from app.models.short_url import ShortUrl
from rest_framework import serializers
class ShortUrlSerializer(serializers.ModelSerializer):
class Meta:
model = ShortUrl
exclude = ()
class ShortUrlDetailSerializer(ShortUrlSerializer):
class Meta(ShortUrlSerializer.Meta):
exclude = ()
6. 接口部分:
# -*- coding: utf-8 -*-
from django.conf import settings
from django.shortcuts import redirect
import rest_framework_filters as filters
from rest_framework import permissions, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.status import HTTP_400_BAD_REQUEST
from app.models.short_url import ShortUrl
from utils.base62 import encode
from app.serializers.short_url import ShortUrlSerializer, ShortUrlDetailSerializer
from app.viewsets.mixin import ListDetailMixin
__author__ = 'JayChen'
class ShortUrlFilter(filters.FilterSet):
class Meta:
model = ShortUrl
fields = {}
class ShortUrlViewSet(viewsets.ModelViewSet, ListDetailMixin):
"""短链接接口"""
permission_classes = (permissions.IsAdminUser,)
queryset = ShortUrl.objects.all()
serializer_class = ShortUrlSerializer
action_serializers = {
'retrieve': ShortUrlDetailSerializer,
'list': ShortUrlSerializer,
}
filter_class = ShortUrlFilter
search_fields = ('short_url',)
@action(detail=False, methods=['POST'], url_path='to-short', permission_classes=(permissions.IsAuthenticated,))
def to_short(self, request):
"""原链接转短链接的接口"""
original_url = request.data.get('original_url')
deadline = request.data.get('deadline')
if original_url:
obj, created = ShortUrl.objects.get_or_create(original_url=original_url, created_by=request.user)
if created:
obj.short_url = f'{settings.DOMAIN}api/v1/{encode(obj.pk)}/render-original/'
if deadline:
obj.deadline = deadline
obj.save(update_fields=['short_url', 'deadline'])
obj.refresh_from_db()
_serializer = self.get_serializer_class()
return Response(_serializer(obj, many=False).data)
raise HTTP_400_BAD_REQUEST
@action(detail=False, methods=['GET'], url_path='(?P<encode_id>[0-9a-fA-Z]+)/render-original',
permission_classes=(permissions.AllowAny,))
def render_original(self, request, encode_id):
"""短链接转发原链接的接口"""
short_url = f'{settings.DOMAIN}api/v1/{encode_id}/render-original/'
obj = ShortUrl.objects.filter(short_url=short_url).first()
if obj:
return redirect(obj.original_url)
raise HTTP_400_BAD_REQUEST
注:settings.DOMAIN指的是在settings.py文件中设置一个叫DOMAIN的字段,值是自己的域名
我在这里自定义了两个接口,to_short()和render_original(),分别是:
- to_short():原链接转短链接的接口
- render_original():短链接转发原链接的接口
to_short()方法详解:
接收用户发送过来的两个数据:original_url和deadline ,即:原链接和失效时间,通过get_or_create()方法,先查询数据库表中没有此用户的这个原链接,如果没有,就新建一条数据,对新建数据的id进制转换得到短链接并保存,如果数据库有相关的数据,就只需要更新失效时间,没有失效时间就不用更新了,最后序列化对象返回数据。
render_original()方法详解:
从用户访问的短链接里面获得encode_id,这个是数据库表自增的id经过进制转换得到的字符串,得到短链接之后,在数据库中查询,如果能查得到,取出查询结果的原链接并转发到原链接对应的网址。
注:功能还没完善,例如短链接过期失效的处理,作者最近年末没时间去折腾,有兴趣的读者可以自己完善哈!