基于Django+DRF实现长链接转短链接

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. 实现思路:

  1. 通过mysql数据库,建立原链接和短链接的关系并存储起来;
  2. 通过进制转换,把10进制转为62进制,能大大缩短字符串的长度;
  3. 用户访问短链接的时候转发到原来的链接。

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经过进制转换得到的字符串,得到短链接之后,在数据库中查询,如果能查得到,取出查询结果的原链接并转发到原链接对应的网址。

注:功能还没完善,例如短链接过期失效的处理,作者最近年末没时间去折腾,有兴趣的读者可以自己完善哈!

上一篇:Freemarker常用指令使用范例


下一篇:sublime text _注册码