基于openresty+lua+redis 实现短链接跳长链接服务

基于openresty+lua+redis 实现短链接跳长链接服务

一、介绍

公司项目需要给客户发送短信,短信中包括营销的链接,希望链接为短链接模式,跳转到正常长链接。
image.png

二、Java 生成唯一编号工具类

原理是 基于Redis存储利用进位制思想生成不重复的随机字符串

import com.circue.common.base.constant.RedisConstants;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Random;

/**
 * 生成随机 唯一字符串
 *
 * @author Parker
 * @date 2022-05-31
 */
@Slf4j
@AllArgsConstructor
@Component
public class RandomSoleStrFactory {

    /**
     * 加密串组
     * 随机排列,保障串的随机性
     */
    private final static char[] DIGITS = {
            'i', 'B', 'l', '8', 'Y', 'n',
            '0', 'K', 'y', 'W', 'p', 'F',
            'A', 'h', 'J', 'x', 'E', '9',
            '6', 'd', 'U', 'g', 'P', 'j',
            'c', 'Q', '2', 'O', 'b', 'N',
            'a', 'k', '5', 'R', 'e', 'M',
            'o', 'C', 'q', '4', 'S', 't',
            'u', 'T', 'v', 'D', '3', 'z',
            'G', 'w', 'I', 'r', 'L', '1',
            's', 'H', 'f', '-', 'X', '7',
            'm', 'Z', '_', 'V'
    };


    /**
     * 设置字符数组
     * 可以添加任意不重复字符,提高能转换的进制的上限
     */
    private final static char[] CHS = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
            'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
            'W', 'X', 'Y', 'Z',
            'a', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'm',
            'n', 'q', 'r', 't', 'y'
    };

    /**
     * 用于生成补位随机串
     */
    private final static char[] ADDITIONAL_DIGITS = {
            '.', '~', '*', '$', '@'
    };

    /**
     * 自增值缓存key
     */
    private final static String INCREMENT_KEY = RedisConstants.PREFIX_SORT_URL_INCR;

    private final RedisTemplate<String, Object> redisTemplate;


    /**
     * 生成六位字符串
     */
    public String randomStrSix() {
        Long increment = redisTemplate.opsForValue().increment(INCREMENT_KEY);
        return convert(increment);
    }

    /**
     * 把10进制的数字转换成64进制
     * 目前最大支持:68719476735
     */
    private static String convert(Long convertNumber) {
        if (convertNumber == null) {
            throw new IllegalArgumentException("待转换参数有误");
        }
        long tempNum = convertNumber;
        char[] buf = new char[DIGITS.length];
        int charPos = DIGITS.length;
        long mask = DIGITS.length - 1;
        do {
            buf[--charPos] = DIGITS[(int) (convertNumber & mask)];
            convertNumber >>>= 6;
        } while (convertNumber != 0);
        String randomStr = new String(buf, charPos, (64 - charPos));
        if (randomStr.length() > 6) {
            throw new IllegalArgumentException(String.format("带转换参数%s已超过可处理的数字范围。", tempNum));
        }
        randomStr = randomStr() + randomStr;
        return randomStr.substring(randomStr.length() - 6);
    }

    /**
     * 随机补位串
     */
    private static String randomStr() {
        Random random = new Random();
        int length = ADDITIONAL_DIGITS.length;
        StringBuilder randomStr = new StringBuilder();
        for (int i = 0; i < length; i++) {
            randomStr.append(ADDITIONAL_DIGITS[random.nextInt(length)]);
        }
        return randomStr.toString();
    }



    /**----------------------其他实现方式------------------------*/
    /**
     * 转换方法
     */
    private static String transRadix(Long convertNumber) {
        StringBuilder sb = new StringBuilder();
        while (convertNumber != 0) {
            sb.append(CHS[(int) (convertNumber % CHS.length)]);
            convertNumber = convertNumber / CHS.length;
        }
        return sb.reverse().toString();
    }



    /**
     * 对数器
     * @param args
     */
    public static void main(String[] args) {
        List<String> urls = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            String convert = convert((long) i);
            if(urls.contains(convert)){
                throw new RuntimeException("重复了");
            }
            urls.add(convert);
            System.out.println("https://url.cn/" + convert);
        }
    }
}


有了工具类后,可以将生成好的URL 按照redis hash 存入 redis中
例如 hash#sorturl *@lb0 https://www.jd.com

三、Nginx 主配置文件


## Openrestry 的 http 中加入lua本地缓存
http {
    ...   
    # 添加共享字典,也就是本地缓存,名称叫做:dis_cache,大小150m
    lua_shared_dict dis_cache 150m;
    ...
}

四、Lua脚本 vim sort_url.lua

local function close_redis(red)  
    if not red then  
        return  
    end  
    --释放连接(连接池实现)  
    local pool_max_idle_time = 10000 --毫秒  
    local pool_size = 100 --连接池大小  
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  
    if not ok then  
        ngx.say("set keepalive error : ", err)  
    end  
end 


--字符串分隔方法
-- str       源字符串
-- sep       匹配字符串
--
-- 成功返回替换后的字符串,失败返回源字符串
function string.split(str, sep)
    str = tostring(str)
    sep = tostring(sep)
    if (sep == "") then return false end
    local pos, arr = 0, {}
    for st, sp in function() return string.find(str, sep, pos, true) end do
        table.insert(arr, string.sub(str, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(str, pos))
    return arr
end

-- 字符串替换【不执行模式匹配】
-- s       源字符串
-- pattern 匹配字符串
-- repl    替换字符串
--
-- 成功返回替换后的字符串,失败返回源字符串
function string.replace(s, pattern, repl)
    if ngx.null == s then
        return ngx.null
    end
    
    local i,j = string.find(s, pattern, 1, true)
    if i and j then
        local ret = {}
        local start = 1
        while i and j do
            table.insert(ret, string.sub(s, start, i - 1))
            table.insert(ret, repl)
            start = j + 1
            i,j = string.find(s, pattern, start, true)
        end
        table.insert(ret, string.sub(s, start))
        return table.concat(ret)
    end
    return s
end

-- 获取以GET方式传参的数据
local uri = ngx.var.uri
local uriArray = string.split(uri, "/")
local key = uriArray[#uriArray]

--尝试从本地缓存中获取
local cache_ngx = ngx.shared.dis_cache;
--根据KEY 获取本地缓存数据
local jumpUrl = cache_ngx:get('sort_url_cache_'..key);

-- 判断本地缓存是否为空
if jumpUrl == "" or jumpUrl == nil or ngx.null == jumpUrl then
    local host = "Redis数据库地址"
    local password = "Redis数据库密码"
    local port = 6379
    local database = 0
    local timeout = 2000
    
    -- 本地缓存为空,向redis中查询
    -- 导入redis module
    local redis = require("resty.redis");
    -- 获取连接对象
    local red = redis:new()
    -- 设置连接超时时间
    red:set_timeout(2000)
    -- redis 的 ip 端口
    local ok, err = red:connect(host, port)
    -- redis设置的密码
    ok, err = red:auth(password)
    
    -- reids连接失败的时候
	if not ok then
    	ngx.log(ngx.debug, "redis connection error:" .. err);
    	return
	end
    red:select(database)

    -- 尝试从redis中获取
    -- redis hash key == hash#:sort_url
    jumpUrl = string.replace(red:hget("hash#:sort_url", key), "\"", "");

    -- 判断redis中的缓存是否为空
    if ngx.null ~= jumpUrl then
        -- 在redis中有数据,存入本地缓存,30分钟过期时间
        cache_ngx:set('sort_url_cache_'..key, jumpUrl, 30*60);
    end 
    close_redis(red)
end
 
-- 在本地缓存中有数据,直接输出
if jumpUrl == "" or jumpUrl == nil or ngx.null == jumpUrl then
    -- 404
    ngx.header.content_type="content-type: text/html"
    ngx.exit(ngx.HTTP_NOT_FOUND)
else
    -- 301 重定向
    ngx.redirect(jumpUrl, ngx.HTTP_MOVED_PERMANENTLY)
end  

五、Nginx 业务代码直接取Redis缓存


server
{
    ...
    # 查询Redis的Lua脚本
    location / {
	# 如果使用阿里云Redis 需要配置resolver
	resolver 8.8.8.8;
        # 酌情考虑是否需要跨域
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers X-Requested-With;
        add_header Access-Control-Allow-Methods GET,POST,OPTIONS;

        # Lua脚本
        content_by_lua_file 存储路径/sort_url.lua;
    }
    ...
}

六、访问接口

https://短域名/*@lb0

本文由 在码圈 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
您可以自由的转载和修改,但请务必注明文章来源并且不可用于商业目的。
本站部分内容收集于互联网,如果有侵权内容、不妥之处,请联系我们删除。敬请谅解!
原文链接:https://www.bedebug.com/archives/lua-sorturl
最后更新于:2022-06-06 11:00:26

请博主喝咖啡 ☕.