简易加解密算法,自用短链接解析访问方案

前言

短链接作用

  1. 给用户发送链接地址的时候,避免字符过长,避免短信费用增加,以及用户点击或者复制时出现问题。
  2. 隐藏真实链接,避免参数等信息直接暴露出来
  3. 做链接失效时,不用去动真实链接,可以直接在短链接这里拦截操作

百度短网址

项目之前使用的是百度短网址,本来是挺好用的,但是后面开始要求 网站注册,然后又分长期和短期,然后又开始又要收费啦,最关键的一点,有时候链接里面包含一些关键字的时候会被误判为违规字符,然后无法生成。这个就比较影响项目了,后面就想着自己实现一套,自己内部使用 redis 然后一个简单不重复的加密算法,数据量并发什么的,单公司内部应用是可以抗住。

实现

  1. 使用雪花算法生成一个唯一 id
  2. 使用生成的 id 调用 encode 加密方法,获取一个 不会重复的 字母串
  3. 使用特殊标识+字母串作为 key , 真实网址作为 value ,存入 redis 及数据库中 ,返回 访问地址加字母串
  4. 访问时,通过字母串查询到真实地址并转发

代码

加密代码

import java.util.Stack;

/**
 * @Description 加解密算法
 * @Author ChengQichuan
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020/4/26
 */
public class PECode {

    /**
     * 打乱改字符数组的组合顺序,可以得到不同的转换结果
     */
    private static final char[] array = { 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g',
            'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '8', '5', '2', '7', '3', '6', '4', '0', '9', '1',
            'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z', 'X',
            'C', 'V', 'B', 'N', 'M' };

    /**
     * @param number double类型的10进制数,该数必须大于0
     */
    public static String encode(Long number) {
        double rest = number;
        // 创建栈
        Stack<Character> stack = new Stack<Character>();
        StringBuilder result = new StringBuilder(0);
        while (rest >= 1) {
            // 进栈,
            stack.add(array[new Double(rest % array.length).intValue()]);
            rest = rest / array.length;
        }
        for (; !stack.isEmpty();) {
            // 出栈
            result.append(stack.pop());
        }
        return result.toString();

    }

    /**
     * 支持范围是A-Z,a-z,0-9,+,-
     *
     */
    public static Long decode(String str) {
        // 倍数
        int multiple = 1;
        Long result = 0L;
        Character c;
        for (int i = 0; i < str.length(); i++) {
            c = str.charAt(str.length() - i - 1);
            result += decodeChar(c) * multiple;
            multiple = multiple * array.length;
        }
        return result;
    }

    private static int decodeChar(Character c) {
        for (int i = 0; i < array.length; i++) {
            if (c == array[i]) {
                return i;
            }
        }
        return -1;
    }


}

Service 实现

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cf.ai.common.utils.PECode;
import com.cf.ai.model.dao.master.entity.CfAiDwz;
import com.cf.ai.model.dao.redis.RedisUtils;
import com.cf.ai.common.utils.SnowFlake.IdSnowFlake;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description
 * @Author ChengQichuan
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020/5/11
 */
@Service
public class DwzService {

    /**
     * 服务对外的地址
     */
    @Value("${dwz.url}")
    private String selfUrl;

    /**
     * 有效时间
     */
    @Value("${dwz.time}")
    private long time;

    /**
     * 拼接地址
     */
    private String urlStr = "/dwz/go/";



    @Autowired
    RedisUtils redisUtils;

    @Autowired
    CfAiDwzService cfAiDwzService;

    /**
     * 创建短连接
     * @param longUrl
     * @return
     */
    public Map create(String longUrl){
        Map map = new HashMap();
        //雪花算法id
        Long snowFlakeId = Long.valueOf(IdSnowFlake.nextId());
        String url = PECode.encode(snowFlakeId);

        if(StringUtils.isBlank(url)){
            map.put("error","生成失败");
        }

        //生成的短链接
        String shortUrl = selfUrl + urlStr + url;

        //保存redis
        redisUtils.set("dwz:" + url, longUrl, time);

        //保存数据库
        CfAiDwz cfAiDwz = new CfAiDwz();
        cfAiDwz.setCreatedTime(new Date());
        cfAiDwz.setLongUrl(longUrl);
        cfAiDwz.setUrl(url);
        cfAiDwzService.save(cfAiDwz);

        map.put("shortUrl",shortUrl);
        return map;
    }


    /**
     * 访问url
     * @param url
     * @return
     */
    public Map goUrl(String url){
        Map map = new HashMap();

        // 先从redis取
        String longUrl = String.valueOf(redisUtils.get("dwz:" + url));
        if (longUrl == null || "null".equals(longUrl)){
            //如果 redis 没取到,就从 数据库取
            QueryWrapper queryWrapper = new QueryWrapper();
            queryWrapper.eq("URL",url);
            queryWrapper.orderByDesc("CREATED_TIME");
            CfAiDwz cfAiDwz = cfAiDwzService.getOne(queryWrapper);
            if (cfAiDwz == null){
                map.put("error","短连接有误");
            }
            longUrl = cfAiDwz.getLongUrl();
        }

        map.put("longUrl",longUrl);
        return map;
    }


    /**
     * 查询原地址
     * @param shortUrl
     * @return
     */
    public Map query(String shortUrl){
        //还原长链接
        String splitStr = selfUrl + urlStr;
        String[] ArrayStr = shortUrl.split(splitStr);
        Map map = goUrl(ArrayStr[1]);
        return map;
    }
}

Controller 实现

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cf.ai.api.common.annotation.ApiLog;
import com.cf.ai.api.common.exception.ApiException;
import com.cf.ai.api.common.response.Result;
import com.cf.ai.model.service.dwz.DwzService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description 短链接
 * @Author ChengQichuan
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020/4/26
 */
@Api(tags = "短链接")
@Controller
@RequestMapping("/api/dwz")
public class DwzController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    DwzService dwzService;

    @ApiLog(apiName = "短网址生成")
    @ApiOperation("短网址生成")
    @PostMapping("/create")
    @ResponseBody
    public Result create(
            @ApiParam(name="链接地址",value="{\"longUrl\":\"http://localhost\"}",required=true)
            @RequestBody JSONObject jsonObject){
        logger.info("===================== 短网址生成接口 /create:{}", JSON.toJSONString(jsonObject));
        String longUrl = jsonObject.getString("longUrl");
        if(StringUtils.isBlank(longUrl)){
            throw new ApiException("链接不能为空");
        }


        String regEx="^[hH][tT][tT][pP]([sS]?):\\/\\/(\\S+\\.)+\\S{2,}$";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(longUrl);
        if (!m.matches() || m.start() != 0){
            return Result.fail("网址不合规:"+longUrl);
        }

        Map map = dwzService.create(longUrl);
        if (map.get("error") != null){
            return Result.fail(String.valueOf(map.get("error")));
        }

        String shortUrl = String.valueOf(map.get("shortUrl"));

        logger.info("=====================短网址生成接口 shortUrl:{}", shortUrl);

        return Result.ok(shortUrl);
    }


    @ApiLog(apiName = "短网址访问")
    @ApiOperation("短网址访问")
    @GetMapping("/go/{url}")
    public String goUrl(@PathVariable String url){
        logger.info("=====================短网址访问接口 url:{}", url);

        if(StringUtils.isBlank(url)){
            throw new ApiException("链接不能为空");
        }

        Map map = dwzService.goUrl(url);
        if (map.get("error") != null){
            throw new ApiException(String.valueOf(map.get("error")));
        }
        String longUrl = String.valueOf(map.get("longUrl"));
        return "redirect:" + longUrl;
    }


    @ApiLog(apiName = "短网址还原")
    @ApiOperation("短网址还原")
    @PostMapping("/query")
    @ResponseBody
    public Result query(@RequestBody JSONObject jsonObject){
        logger.info("=====================短网址还原接口 query:{}", JSON.toJSONString(jsonObject));
        String shortUrl = jsonObject.getString("shortUrl");
        if(StringUtils.isBlank(shortUrl)){
            throw new ApiException("链接不能为空");
        }

        Map map = dwzService.query(shortUrl);

        if (map.get("error") != null){
            throw new ApiException(String.valueOf(map.get("error")));
        }
        String longUrl = String.valueOf(map.get("longUrl"));

        return Result.ok(longUrl);
    }

}

雪花算法

生成唯一 id 使用

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

/**
 * 雪花算法
 */
public class IdSnowFlake {
    private final static long twepoch = 12888349746579L;
    // 机器标识位数
    private final static long workerIdBits = 5L;
    // 数据中心标识位数
    private final static long datacenterIdBits = 5L;

    // 毫秒内自增位数
    private final static long sequenceBits = 12L;
    // 机器ID偏左移12位
    private final static long workerIdShift = sequenceBits;
    // 数据中心ID左移17位
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    //sequence掩码,确保sequnce不会超出上限
    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
    //上次时间戳
    private static long lastTimestamp = -1L;
    //序列
    private long sequence = 0L;
    //服务器ID
    private long workerId = 1L;
    private static long workerMask = -1L ^ (-1L << workerIdBits);
    //进程编码
    private long processId = 1L;
    private static long processMask = -1L ^ (-1L << datacenterIdBits);

    private static IdSnowFlake snowFlake = null;

    static{
        snowFlake = new IdSnowFlake();
    }
    public static synchronized String nextId(){
        return String.valueOf(snowFlake.getNextId());
    }

    private IdSnowFlake() {

        //获取机器编码
        this.workerId=this.getMachineNum();
        //获取进程编码
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        this.processId=Long.valueOf(runtimeMXBean.getName().split("@")[0]).longValue();

        //避免编码超出最大值
        this.workerId=workerId & workerMask;
        this.processId=processId & processMask;
    }

    public synchronized long getNextId() {
        //获取时间戳
        long timestamp = timeGen();
        //如果时间戳小于上次时间戳则报错
        if (timestamp < lastTimestamp) {
            try {
                throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //如果时间戳与上次时间戳相同
        if (lastTimestamp == timestamp) {
            // 当前毫秒内,则+1,与sequenceMask确保sequence不会超出上限
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 当前毫秒内计数满了,则等待下一秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        // ID偏移组合生成最终的ID,并返回ID
        long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
        return nextId;
    }

    /**
     * 再次获取时间戳直到获取的时间戳与现有的不同
     * @param lastTimestamp
     * @return 下一个时间戳
     */
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 获取机器编码
     * @return
     */
    private long getMachineNum(){
        long machinePiece;
        StringBuilder sb = new StringBuilder();
        Enumeration<NetworkInterface> e = null;
        try {
            e = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e1) {
            e1.printStackTrace();
        }
        while (e.hasMoreElements()) {
            NetworkInterface ni = e.nextElement();
            sb.append(ni.toString());
        }
        machinePiece = sb.toString().hashCode();
        return machinePiece;
    }
}

相关文章

转载请注明: 转载自 浮生一程
本文链接地址 简易加解密算法,自用短链接解析访问方案
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇