Skip to content

short link coderdream

CoderDream edited this page Apr 26, 2022 · 1 revision
# 应用名称
spring.application.name=short-link

# 应用服务 WEB 访问端口
server.port=8088

#自定义进制
app.config.customSystem=62
#短链接总长度
app.config.totalBit=8
#机器占位
app.config.machineBit=1
#计数器占位
app.config.counterBit=1
#缓存过期时长
app.config.expireSec=3600

app.model=emit

# Maximum number of connections that the server accepts and processes at any given time.
server.tomcat.max-connections=400
# Maximum number of worker threads.
server.tomcat.threads.max=200
# Minimum number of worker threads.
server.tomcat.threads.min-spare=64 
package com.coderdream.utils;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
@Slf4j
public class Config {
    /**
     * 进制
     */
    @Value("${app.config.customSystem}")
    public Long CUSTOM_SYSTEM;

    /**
     * 总位数
     */
    @Value("${app.config.totalBit}")
    public Integer TOTAL_BIT;

    /**
     * 机器占位
     */
    @Value("${app.config.machineBit}")
    public Integer MACHINE_BIT;

    /**
     * 计数器占用的位数
     */
    @Value("${app.config.counterBit}")
    public Integer COUNTER_BIT;

    /**
     * 编码占用位数
     */
    public Integer CODE_BIT;

    /**
     * 号个数最大值
     */
    public Long CODE_NUM_MIX = 1L;

    /**
     * 计数器个数
     */
    public Long COUNTER_CNT = 1L;

    /**
     * 短链缓存时长(s)
     */
    @Value("${app.config.expireSec}")
    public Long EXPIRE_SEC;

    @PostConstruct
    public void init() {

        check();

        CODE_BIT = TOTAL_BIT - MACHINE_BIT - COUNTER_BIT;

        // 计算号池塘的最大值
        for (int i = 0; i < CODE_BIT; i++) {
            CODE_NUM_MIX *= CUSTOM_SYSTEM;
        }

        // 计算计数器个数
        for (int i = 0; i < COUNTER_BIT; i++) {
            COUNTER_CNT *= CUSTOM_SYSTEM;
        }
    }

    private void check() {
        if (TOTAL_BIT <= 0 || MACHINE_BIT < 0 || COUNTER_BIT <= 0) {
            log.error("TOTAL_BIT={},MACHINE_BIT={},COUNTER_BIT={} 3参数存在不合理参数,请检查", TOTAL_BIT, MACHINE_BIT, COUNTER_BIT);
            throw new RuntimeException("存在不合理参数");
        }
        if (TOTAL_BIT - MACHINE_BIT - COUNTER_BIT <= 0) {
            log.error(
                "TOTAL_BIT={},MACHINE_BIT={},COUNTER_BIT={} 3参数存在不合理导致TOTAL_BIT - MACHINE_BIT - COUNTER_BIT <= 0,请检查",
                TOTAL_BIT, MACHINE_BIT, COUNTER_BIT);
            throw new RuntimeException("存在不合理参数");
        }
    }
}
package com.coderdream.service.impl;

import com.coderdream.helper.GuavaCacheHelper;
import com.coderdream.model.ShortLinkBean;
import com.coderdream.service.LinkService;
import com.coderdream.utils.Config;
import com.coderdream.utils.ShortLinkComponent;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class LinkServiceImpl implements LinkService {
    @Resource
    private Config config;

    @Resource
    private ShortLinkComponent shortLinkComponent;

    @Resource
    private GuavaCacheHelper guavaCacheHelper;

    @Override
    public String getShortLink(String longLink) {
        String code = shortLinkComponent.createShortLinkCode(longLink);

        ShortLinkBean shortLinkBean = new ShortLinkBean();
        shortLinkBean.setShortLink(code);
        shortLinkBean.setLongLink(longLink);
        shortLinkBean.setExpireTime(System.currentTimeMillis() + config.EXPIRE_SEC * 1000);
        guavaCacheHelper.put(code, shortLinkBean);

        return code;
    }

    @Override
    public String getLongLink(String shortLink) {
        Object value = guavaCacheHelper.get(shortLink);
        if (value == null) {
            return null;
        }
        return ((ShortLinkBean) value).getLongLink();
    }
}
package com.coderdream.model;

import lombok.Data;

@Data
public class ShortLinkBean {

    /**
     * id
     */
    private Long id;

    /**
     * 长连接
     */
    private String longLink;

    /**
     * 长连接对应的短链接
     */
    private String shortLink;

    /**
     * 短链接过期时间
     */
    private Long expireTime;

}
package com.coderdream.helper;

import lombok.extern.slf4j.Slf4j;

import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

/**
 *
 */
@Service
@Slf4j
public class FileOperateHelper {
    public String readFile(String fileName) {
        ClassPathResource classPathResource = new ClassPathResource(fileName);
        InputStream inputStream = null;
        StringBuilder stringBuilder = new StringBuilder();
        try {
            inputStream = classPathResource.getInputStream();
            char[] buf = new char[1024];
            Reader r = new InputStreamReader(inputStream, "UTF-8");
            while (true) {
                int n = r.read(buf);
                if (n < 0) {
                    break;
                }
                stringBuilder.append(buf, 0, n);
            }
        } catch (IOException e) {
            log.error("file error", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    log.error("file close error", e);
                }
            }
        }
        return stringBuilder.toString();
    }

    public void writeFile(String fileName, String content) {
        try {
            String basePath = ResourceUtils.getURL("classpath:").getPath() + "/info/";
            File fileExist = new File(basePath);
            // 文件夹不存在,则新建
            if (!fileExist.exists()) {
                fileExist.mkdirs();
            }
            // 获取文件对象
            File file = new File(basePath, fileName);
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.flush();
            fileWriter.close();
        } catch (Exception e) {
            log.error("writeFile error", e);
        }
    }
}
package com.coderdream.helper;

import com.coderdream.utils.Config;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

@Service
@Slf4j
public class GuavaCacheHelper {
    @Resource
    private Config config;

    private Cache<Object, Object> cache;

    @PostConstruct
    public void init() {
        cache = CacheBuilder.newBuilder()
            //设置并发级别为8,并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(8)
            //设置缓存容器的初始容量为10
            .initialCapacity(10)
            //设置缓存最大容量为100,超过100之后就会按照LRU最近最少使用算法来移除缓存项,生产环境可设置为 100000000
            .maximumSize(100000000)
            //是否需要统计缓存情况,该操作消耗一定的性能,生产环境应该去除
            .recordStats()
            //设置写缓存后n秒钟过期
            .expireAfterWrite(config.EXPIRE_SEC, TimeUnit.SECONDS)
            //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
            //.expireAfterAccess(17, TimeUnit.SECONDS)
            //只阻塞当前数据加载线程,其他线程返回旧值
            //.refreshAfterWrite(13, TimeUnit.SECONDS)
            //设置缓存的移除通知
            .removalListener(notification -> {
                log.info(notification.getKey() + " " + notification.getValue() + " 被移除,原因:" + notification.getCause());
            })
            //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
            .build();
    }

    /**
     * 获取缓存
     */
    public Object get(String key) {
        return StringUtils.isEmpty(key) ? null : cache.getIfPresent(key);
    }

    /**
     * 放入缓存
     */
    public void put(String key, Object value) {
        if (StringUtils.isNotEmpty(key) && value != null) {
            cache.put(key, value);
        }
    }

}