Ali architects share: redis short link generation practice, netizen: can you still have this operation?

Posted by TheDeadPool on Thu, 03 Mar 2022 16:51:11 +0100

background

At present, there are various promotion addresses of products in various circles. Due to the long URL address, unsightly, inconvenient collection, release, dissemination and the limitation of the number of words of various posts, wechat and microblog are using short link technology. Recently, due to the use of three-party generation and parsing short link services, restrictions on use, preparation of charges, inconvenient statistical analysis, inconvenient flow control and other problems, we decided to build a short address service by ourselves.

principle

For example, http://dx.*.com/15uOVS this short address

Step 1, the browser requests this address

Step 2: go to the short address server through DNS and restore the original long address corresponding to the short address.

Step 3, request http 301 or 302 to the original long address

Step 4: the browser gets the response of the original long address

realization

The core of short address service is the conversion and mapping algorithm of short address and long address.

The simplest algorithm is to make the MD5 summary of the original long address as key and the long address as value. Put the key value into the server cache, such as redis.

During reverse parsing, the key is solved through the URL. For example, the above short address key = 15uOVS. Then get the original long address value from the key cache to restore the URL address.

The MD5 summary has several obvious problems:

1. The length of the short address is limited. For example, the data length after MD5 is 32 bits, which needs to be processed in segments and loops to make the short address short enough

2. The hash collision problem of MD5 has a certain probability of repetition. To solve this problem, we need to constantly improve the complexity of the algorithm, some gains outweigh the losses

Of course, there are more than MD5 implementation algorithms. You can Google by yourself.

code implementation

1: Short connection tools

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
/**
 * No matter how long the generated URL is, it will be realized
 *
 * @author CHX
 */
 
public class ShortURL {
 
    private static String[] chars = new String[]{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
            "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8",
            "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
            "U", "V", "W", "X", "Y", "Z"};
 
 
    /**
     * First, get the fixed format content from the URL*
     * @param longUrl Original url
     * @param yuMing  domain name
     */
    public static String myTestShort(String longUrl, String yuMing) {
        String newurl = "";
        String regex = "(http://|https://)" + yuMing + "(.*)";
        Pattern r = Pattern.compile(regex);
        // Now create the matcher object
        Matcher m = r.matcher(longUrl);
        if (m.find()) {
            String url = m.group(2);
            if (url != null) {
                // Here is the generated four bit short connection
                newurl = changes(url);
                //System.out.println(m.group(1) + yuMing + "/" + changes(url));
            }
        }
        return newurl;
    }
    /**
     * Coding idea: Considering base64 coding, there are only [0-9][a-z][A-Z] characters in the url. There are 26 + 26 + 10 = 62 characters in total, and the corresponding mapping table is 62 base
     *
     * @param value
     * @return
     */
    public static String changes(String value) {
        // Get base64 encoding
        String stringBase64 = stringBase64(value);
        // Remove the last = = (this is a feature of base64 and ends with = =)
        stringBase64 = stringBase64.substring(0, stringBase64.length() - 2);
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        // Using md5 to generate 32-bit fixed length string
        String mid = new String(bytesToHexString(md5.digest(stringBase64.getBytes())));
        StringBuilder outChars = new StringBuilder();
        for (int i = 0; i < 4; i++) {
            //In groups of eight
            String sTempSubString = mid.substring(i * 8, i * 8 + 8);
            // Try to reduce the number of eight characters in hexadecimal to less than 62, so take the remainder and replace it with the corresponding alphanumeric
            outChars.append(chars[(int) (Long.parseLong(sTempSubString, 16) % chars.length)]);
        }
        return outChars.toString();
    }
    /**
     * Convert string to base64 encoding*
     * @param text original text
     * @return
     */
    public static String stringBase64(String text) {
        return Base64.getEncoder().encodeToString(text.getBytes());
    }
    /**
     * Convert byte to hexadecimal string*
     * @param src
     * @return
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }
 
}

2: redis configuration

java dependency

<!--springboot Medium redis rely on-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

configuration file

##Port number
# Redis database index (0 by default)
spring.redis.database=0 
# Redis server address
spring.redis.host=192.168.124.20
# Redis server connection port
spring.redis.port=6379 
# Redis server connection password (blank by default)
spring.redis.password=
#Maximum number of connections in connection pool (negative value indicates no limit)
spring.redis.jedis.pool.max-idle=8 
# Maximum blocking waiting time of connection pool (negative value indicates no limit)
spring.redis.jedis.pool.max-wait=
# Maximum free connections in connection pool
# Minimum free connections in connection pool
spring.redis.jedis.pool.min-idle=0 
# Connection timeout (MS)
spring.redis.timeout=300
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
 
/**
 * redis Configuration class
 * @program: springbootdemo
 * @Date: 2019/2/22 15:20
 * @Author: zjjlive
 * @Description:
 */
@Configuration
@EnableCaching //Open annotation
public class RedisConfig extends CachingConfigurerSupport {
 
    /**
     * retemplate Related configuration
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
 
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // Configure connection factory
        template.setConnectionFactory(factory);
 
        //Use Jackson2JsonRedisSerializer to serialize and deserialize the value value of redis (JDK serialization is used by default)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
 
        ObjectMapper om = new ObjectMapper();
        // Specify the fields to be serialized, field,get and set, and modifier range. ANY includes private and public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // Specify the type of serialized input. The class must be non final modified. Final modified classes, such as string and integer, will run exceptions
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);
 
        // Values are serialized in json
        template.setValueSerializer(jacksonSeial);
        //Use StringRedisSerializer to serialize and deserialize the key value of redis
        template.setKeySerializer(new StringRedisSerializer());
 
        // Set hash key and value serialization mode
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();
 
        return template;
    }
 
    /**
     * Data operation on hash type
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }
 
    /**
     * Operation on redis string type data
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }
 
    /**
     * Data operation of linked list type
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }
 
    /**
     * Data operations on unordered collection types
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }
 
    /**
     * Data operations on ordered collection types
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
 
}

3: Short connection usage

import com.eltyl.api.util.RedisUtil;
import com.eltyl.api.util.ShortURL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/redis/")
public class EndLinkContronller {
    @Autowired
    private RedisUtil redisUtil;
    @RequestMapping("save")
    public String save(){
        String plainUrl = "http://www.*.com/index.php?s=/Index/index/id/1.html";
        String endlink = ShortURL.myTestShort(plainUrl,"www.*.com");
        System.out.println(endlink);
        redisUtil.set(endlink,plainUrl);
        return "success";
    }

Finally, build another project to intercept, and check whether there are relative links in redis

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@RestController
public class IndexContronller {
    @Autowired
    private RedisUtil redisUtil;
    @RequestMapping("/**")
    public void save(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("What do you want to do:"+request.getServletPath());
        String newurl = request.getServletPath().replace("/","");
        if(redisUtil.get(newurl)!=null){
 
            System.out.println("existence");
            //return "redirect:http://www.*.com";
            response.sendRedirect("http://www.*.com");
        }else{
            System.out.println("non-existent");
            //return "redirect:http://new.*.com";
            response.sendRedirect("http://news.*.com");
        }
        //return "success";
    }
}

Code words are not easy. If you think this article is useful to you, please give me one button three times! Pay attention to the author, there will be more dry goods to share in the future, please continue to pay attention!

Topics: Java Spring