Приходите и создайте свой собственный сервис коротких ссылок!

HTTPS

Что такое короткая ссылка?

Так называемая короткая ссылка предназначена для преобразования обычного URL-адреса в относительно короткий URL-адрес, а контент, полученный в результате посещения, остается неизменным.

Например, для такой ссылкиnuggets.capable/post/684490…, с помощью сервиса коротких ссылок можно преобразовать его во что-то вроде этогоhttp://xxx/abc(По какой-то неописуемой причине и под влиянием каких-то форс-мажоров этот URL на самом деле составлен мной). Это кажется намного проще (.-`ω´-)

Как реализованы короткие ссылки?

Фундаментальный

Проще говоря, когда мы входимhttp://xxx/abcПосле этого он пройдет следующий процесс:

  1. DNS сначала разрешается для полученияhttp://xxxIP-адрес пользователя
  2. Когда DNS получит IP-адрес, он отправит запрос GET на этот адрес, чтобы запросить короткий код abc.
  3. http://xxxСлужба, работающая на сервере, получит исходный URL-адрес через короткий код abc.
  4. Запрос переходит к соответствующему длинному URL-адресу через перенаправление Http, к которому можно получить доступ в обычном режиме.

Среди них перенаправление делится на 301 (постоянное перенаправление) и 302 (временное перенаправление). Благодаря тому свойству, что короткий адрес не изменится после его генерации, использование постоянного перенаправления, безусловно, может снизить нагрузку на сервер, но также невозможно подсчитать количество обращений к странице через короткий адрес. Поэтому, когда требуется анализ данных такого типа, вы можете использовать 302, чтобы перейти к сбору дополнительных данных за счет увеличения нагрузки на сервер.

Реализация часто используемого алгоритма

Алгоритм сжатия контента (алгоритм сжатия MD5)

Используйте алгоритм для прямого сжатия содержимого длинной цепочки, например, получение алгоритма хэширования или алгоритма MD5 для создания 128-битной подписи для длинной ссылки, а затем усекайте подпись до 4–8 бит для использования в качестве короткой. цепной код.

Преимущества: его просто генерировать, и он может поддерживать повторяющиеся запросы длинных ссылок без установления соответствующих отношений.

Недостатки: Поскольку MD5 является алгоритмом сжатия с потерями, неизбежно возникнут повторяющиеся проблемы.

Алгоритм генератора чисел (алгоритм самоинкремента id)

Назначьте идентификационный номер каждой запрошенной длинной ссылке, зашифруйте индекс и используйте его в качестве короткого кода для создания необходимой короткой ссылки. Так как идентификатор является самоувеличивающимся, теоретически сгенерированная короткая ссылка никогда не будет повторяться, поэтому его также называют алгоритмом никогда не повторяющегося.

Плюсы: позволяет избежать проблем с дублированием коротких ссылок.

Недостаток: раскрывается самоувеличивающийся идентификатор, возникает большая угроза безопасности, что приводит к утечке информации о ссылке; необходимо установить соответствующую связь для поддержки повторного запроса длинных ссылок.

Создайте свой собственный сервис коротких ссылок

Здесь мы используем для реализации алгоритм автоинкремента id.

Сначала создайте проект springboot, технологический стекspring boot + spring-data-jpa + mysql, структура проекта следующая:

Затем мы начнем разбираться в требованиях: на самом деле нам нужны только две функции.

  1. Получите соответствующий короткий URL-адрес в соответствии с URL-адресом
  2. Получите доступ к исходному URL-адресу через короткий URL-адрес, то есть перейдите к исходному URL-адресу.

Реализация алгоритма сопоставления URL

Первое, что нужно решить, это长链接重复查询问题. Как упоминалось выше, необходимо установить соответствующие отношения, здесь мы просто используем таблицу для хранения. Соответствующий класс сущностей TranslationEntity выглядит следующим образом:

package com.demo.demosurl.demosurl.model.entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * @author yuanyiwen
 * @create 2019-12-06 21:04
 * @description
 */
@Entity
@Data
public class TranslationEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    /**
     * 真实url(长链)
     */
    private String url;

    /**
     * 转换url(短链)
     */
    private String shortUrl;

}

После直接暴露自增主键造成的链接信息泄露问题. Если подумать просто, если регулярность изменения id 1, 2, 3, 4, ..., то легко угадать следующий, но если регулярность изменения id 1, 32, 18, 97 , ..., а что? Не могу угадать, что будет дальше!

Суть сокрытия возрастающего закона заключается в использовании структуры шифра Фейстеля, цитируя слова большого парня:

Входными данными для алгоритма шифрования Фейстеля является открытый текст длины 2w и ключ K=(K1, K2...,Kn). Пакет открытого текста делится на левую и правую половины L и R, а затем выполняется n циклов итераций.После завершения итерации левая и правая половины объединяются вместе для создания пакета зашифрованного текста.

Алгоритм шифрования Фейстеля может дать очень полезную функцию, то есть в пределах определенного диапазона чисел (2 в n-й степени) каждое число может найти уникальный соответствующий объект в соответствии с ключом, который достигает нашей скрытой цели закона приращения. .

Например, нам дан диапазон чисел 64 (от 2 до 6-й степени), где каждому числу соответствует уникальная случайная соответствующая пара чисел. Случайность здесь генерируется ключом.

Затем мы используем алгоритм для вычисления 1 и 2, и мы обнаружим, что соответствующие числа равны 17 и 25. Это прекрасно решает нашу проблему увеличения чисел, которую внешние пользователи не могут определить по поверхности чисел, которые они увеличивают. И шаблон соответствия для каждого числа отличается.

С этой идеей мы добавляем служебный класс запутывания идентификаторов для сопоставления упорядоченных идентификаторов с неупорядоченными:

package com.demo.demosurl.demosurl.util.encrypt;

/**
 * @author yuanyiwen
 * @create 2019-12-07 22:02
 * @description id混淆工具类
 */
public abstract class NumberEncodeUtil {

    /**
     * 对id进行混淆
     * @param id
     * @return
     */
    public static Long encryptId(Long id) {
        Long sid = (id & 0xff000000);
        sid += (id & 0x0000ff00) << 8;
        sid += (id & 0x00ff0000) >> 8;
        sid += (id & 0x0000000f) << 4;
        sid += (id & 0x000000f0) >> 4;
        sid ^= 11184810;
        return sid;
    }

    /**
     * 对混淆的id进行还原
     * @param sid
     * @return
     */
    public static Long decodeId(Long sid) {
        sid ^= 11184810;
        Long id = (sid & 0xff000000);
        id += (sid & 0x00ff0000) >> 8;
        id += (sid & 0x0000ff00) << 8;
        id += (sid & 0x000000f0) >> 4;
        id += (sid & 0x0000000f) << 4;
        return id;
    }
}

После решения проблемы увеличения числа, следующее, что нужно сделать, это数字压缩与加密. Самый простой способ уменьшить длину числа без изменения его значения — преобразовать его в большее основание. Для одного бита знака, если мы возьмем простые символы без знака (0-9a-zA-Z), это составит ровно 62 общих символа. Таким образом, мы можем преобразовать запутанный десятичный идентификатор в основание 62:

package com.demo.demosurl.demosurl.util.encrypt;

import java.util.Stack;

/**
 * @author yuanyiwen
 * @create 2019-12-07 22:00
 * @description 进制转换工具类
 */
public abstract class ScaleConvertUtil {

    /**
     * 将10进制数字转换为62进制
     * @param number 数字
     * @param length 转换成的62进制长度,不足length长度的高位补0
     * @return
     */
    public static String convert(long number, int length){

        char[] charSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();

        Long rest=number;
        Stack<Character> stack=new Stack<Character>();
        StringBuilder result=new StringBuilder(0);
        while(rest!=0){
            stack.add(charSet[new Long((rest-(rest/62)*62)).intValue()]);
            rest=rest/62;
        }
        for(;!stack.isEmpty();){
            result.append(stack.pop());
        }
        int result_length = result.length();
        StringBuilder temp0 = new StringBuilder();
        for(int i = 0; i < length - result_length; i++){
            temp0.append('0');
        }

        return temp0.toString() + result.toString();
    }
}

Затем вы можете начать реализовывать конкретную реализацию! ServiceImpl содержит два метода:

  • Когда короткий URL-адрес получается из реального URL-адреса, возвращается объект конверсии.
  • Когда реальный URL-адрес получается из короткого URL-адреса, возвращается объект конверсии.

Конкретная реализация выглядит следующим образом, а ключевые идеи отмечены в комментариях:

package com.demo.demosurl.demosurl.service.impl;

import com.demo.demosurl.demosurl.common.CommonConstant;
import com.demo.demosurl.demosurl.dao.TranslationDto;
import com.demo.demosurl.demosurl.model.entity.TranslationEntity;
import com.demo.demosurl.demosurl.model.vo.TranlationVo;
import com.demo.demosurl.demosurl.service.TranslationService;
import com.demo.demosurl.demosurl.util.convertion.EntityVoUtil;
import com.demo.demosurl.demosurl.util.encrypt.NumberEncodeUtil;
import com.demo.demosurl.demosurl.util.encrypt.ScaleConvertUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author yuanyiwen
 * @create 2019-12-06 21:12
 * @description
 */
@Service
public class TranslationServiceImpl implements TranslationService {

    @Autowired
    private TranslationDto translationDto;

    @Override
    public TranlationVo getShortUrlByUrl(String url) {

        // 若不是URL格式,直接返回空
        if(!isHttpUrl(url)) {
            return null;
        }

        TranslationEntity translationEntity = translationDto.findByUrl(url);

        // 如果该实体不为空,直接返回对应的短url
        if(translationEntity != null) {
            return EntityVoUtil.convertEntityToVo(translationEntity);
        }

        // 否则,重新生成转换实体并存入数据库 todo 存入缓存
        translationEntity = new TranslationEntity();

        // 获取当前id并生成短url尾缀
        Long currentId = translationDto.count()+1;
        String shortUrlSuffix = ScaleConvertUtil.convert(NumberEncodeUtil.encryptId(currentId), CommonConstant.LENGTH_OF_SHORT_URL);

        // 短链接拼接
        String shortUrl = CommonConstant.PRIFIX_OF_SHORT_URL + shortUrlSuffix;

        translationEntity.setUrl(url);
        translationEntity.setShortUrl(shortUrl);
        translationDto.save(translationEntity);

        return EntityVoUtil.convertEntityToVo(translationEntity);
    }

    @Override
    public TranlationVo getUrlByShortUrl(String shortUrl) {
        TranslationEntity translationEntity = translationDto.findByShortUrl(shortUrl);
        if(translationEntity != null) {
            return EntityVoUtil.convertEntityToVo(translationEntity);
        }
        return null;
    }

    /**
     * 判断一个字符串是否为URL格式
     * @param url
     * @return
     */
    private boolean isHttpUrl(String url) {
        boolean isUrl = false;
        if(url.startsWith("http://") || url.startsWith("https://")) {
            isUrl = true;
        }
        return isUrl;
    }
}

Предоставьте метод интерфейса для «получения соответствующей короткой ссылки по URL-адресу»:

@PostMapping("/surl")
public ResponseVo<TranlationVo> getShortUrlByUrl(String url) {

    TranlationVo tranlationVo = translationService.getShortUrlByUrl(url);

    if(tranlationVo == null) {
        return ResponseUtil.toFailResponseVo("请检查上传的url格式");
    }

    return ResponseUtil.toSuccessResponseVo(tranlationVo);
}

Наконец, есть некоторые постоянные параметры, которые можно настраивать централизованно, которые извлекаются здесь отдельно, чтобы упростить пользовательское развертывание:

package com.demo.demosurl.demosurl.common;

/**
 * @author yuanyiwen
 * @create 2019-12-07 22:13
 * @description 保存项目中用到的常量
 */
public interface CommonConstant {

    /**
     * 默认生成的短链接后缀长度
     */
    Integer LENGTH_OF_SHORT_URL = 4;


    /**
     * 默认生成的短链接前缀
     */
    String PRIFIX_OF_SHORT_URL = "http://localhost:8090/";


    /**
     * 若输入的短链接不存在,默认跳转的页面
     */
    String DEFAULT_URL = "http://localhost:8090/default";
}

Реализовать переход по URL

Это очень просто, просто проведите длинную цепочку через короткую, а затем используйте перенаправление ModelAndView для перехода:

@GetMapping("{shortUrl}")
public ModelAndView redirect(@PathVariable String shortUrl, ModelAndView mav){

    // 获取对应的长链接(若短链接不存在,则跳转到默认页面)
    TranlationVo tranlationVo = translationService.getUrlByShortUrl(CommonConstant.PRIFIX_OF_SHORT_URL + shortUrl);
    String url = (tranlationVo == null) ? CommonConstant.DEFAULT_URL : tranlationVo.getUrl();

    // 跳转
    mav.setViewName("redirect:" + url);

    return mav;
}

Наконец, проверьте результаты

Запускаем проект, открываем postman и вводим наш URL:

Как видите, сервис уже давно подключенnuggets.capable/post/684490…Преобразование в соответствующую короткую ссылкуhttp://localhost:8090/kvgQвернулся. Заходим и пробуем:

Прыгай успешно! Такой простой сервис коротких ссылок готов.

Улучшения проекта и исходный код

Поскольку это настраиваемый и очень простой сервис коротких ссылок, есть еще много областей, которые можно улучшить, например:

  • Используйте кеш, чтобы установить соответствующую взаимосвязь ссылок, и своевременно очищайте короткие ссылки, которые больше не нужны, чтобы избежать бесконечного расширения данных.
  • Сделайте некоторую статистику, такую ​​как количество посещений, сегменты IP-адресов, устройства, используемые пользователями и т. д.
  • Используйте несколько эмитентов номеров для выпуска чисел в шахматном порядке для решения проблемы синхронизации после преобразования в распределенную систему
  • Добавить страницу интерфейса для людей TuT

Наконец, тихонько поставьте адрес github _(:3”∠)_ :GitHub.com/yuanlie r/su…


Справочная статья:
Блог Woo Woo.cn на.com/yuan уменьшен до/… no.OSCHINA.net/U/2485991/no…