Java之SpringBoot短链接生成

本文介绍了一个基于Dubbo实现的短链接服务,包括生成短链、获取完整链接和增加点击计数的功能。服务中使用了Redis和MySQL进行数据存储,并通过 Scheduled 定时任务清理过期短链。同时,通过线程池异步处理短链点击量的增加,确保高并发场景下的性能。

1、IShortUrlService 接口类

 

public interface IShortUrlService {

    /**
     * 生成短链
     *
     * @param shortUrlRequest
     * @return
     */
    String shortUrlGenerator(ShortUrlRequest shortUrlRequest);

    /**
     * 根据短链获取完整链接
     *
     * @param shortUrl
     * @return
     */
    String getUrl(String shortUrl);

    /**
     * 增加短链点击量
     *
     * @param shortUrl
     * @return
     */
    Boolean addClickCount(String shortUrl);
}

2、ShortUrlServiceImpl 接口实现类

@DubboService
public class ShortUrlServiceImpl implements IShortUrlService {

    private static final Logger log = LoggerFactory.getLogger(ShortUrlServiceImpl.class);

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private ShortUrlMapper shortUrlMapper;

    @Resource
    private SpringApplicationContext springApplicationContext;

    /**
     * 域名匹配正则表达式
     */
    private static final Pattern DOMAIN_NAME_PATTERN = Pattern.compile("http(s)?://[\\w-]+[.\\w-]+(/)?");

    @Override
    public String shortUrlGenerator(ShortUrlRequest shortUrlRequest) {
        String fullUrl = shortUrlRequest.getFullUrl();
        if (ObjectUtils.isEmpty(fullUrl)) {
            throw new BaseException(500, "链接不能为空");
        }
        String[] shortUrls = ShortUrlUtil.generator(fullUrl);
        for (String shortUrl : shortUrls) {
            // 判断短链是否已存在
            String url = springApplicationContext.getBean(IShortUrlService.class).getUrl(shortUrl);
            if (url != null) {
                if (!url.equals(shortUrlRequest.getFullUrl())) {
                    continue;
                }
                return shortUrl;
            }
            // 保存长链接与短链接短对应关系
            ShortUrl shortUrlEntity = new ShortUrl();
            shortUrlEntity.setFullUrl(fullUrl);
            shortUrlEntity.setShotCode(shortUrl);
            shortUrlEntity.setExpirationDate(shortUrlRequest.getExpirationDate());
            Matcher matcher = DOMAIN_NAME_PATTERN.matcher(fullUrl);
            if (!matcher.find()) {
                throw new BaseException(500, "链接解析失败");
            }
            String baseUrl = matcher.group();
            shortUrlEntity.setBaseUrl(baseUrl);
            shortUrlEntity.setSuffixUrl(fullUrl.substring(baseUrl.length() - 1));
            shortUrlMapper.insert(shortUrlEntity);
            return shortUrl;
        }
        throw new BaseException(500, "链接生成失败");
    }

    @Override
    @Cacheable(cacheNames = "shortUrl", key = "#shortUrl", unless = "#result == null")
    public String getUrl(String shortUrl) {
        QueryWrapper<ShortUrl> shortUrlQw = new QueryWrapper<>();
        shortUrlQw.eq("shot_code", shortUrl);
        shortUrlQw.eq("del_flag", DelFlag.NORMAL);
        ShortUrl url = shortUrlMapper.selectOne(shortUrlQw);
        return url == null ? null : url.getFullUrl();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addClickCount(String shortUrl) {
        QueryWrapper<ShortUrl> shortUrlQw = new QueryWrapper<>();
        shortUrlQw.eq("shot_code", shortUrl);
        shortUrlQw.eq("del_flag", DelFlag.NORMAL);
        ShortUrl url = shortUrlMapper.selectOne(shortUrlQw);
        if (url == null) {
            return false;
        }
        url.setTotalClickCount(url.getTotalClickCount() + 1);
        return shortUrlMapper.updateById(url) > 0;
    }

    @Scheduled(cron = "0 0 3 * * * ")
    public void removeShortUrl() {
        QueryWrapper<ShortUrl> queryWrapper = new QueryWrapper<>();
        queryWrapper.le("expirationDate", DateUtil.getTime());
        List<ShortUrl> shortUrls = shortUrlMapper.selectList(queryWrapper);
        //删除缓存数据库
//        for (ShortUrl shortUrl : shortUrls) {
//            redisTemplate.delete(shortUrl.getShotCode());
//        }
        List<Long> shortUrlIds = shortUrls.stream().map(ShortUrl::getId).collect(Collectors.toList());
        //删除mysql数据库
        shortUrlMapper.deleteBatchIds(shortUrlIds);
//        try {
//            Thread.sleep(1000);
//            //再次删除缓存数据库
//            for (ShortUrl shortUrl : shortUrls) {
//                redisTemplate.delete(shortUrl.getShotCode());
//            }
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
    }
}

3、ShortUrlHandler 工具类

public class ShortUrlHandler implements Runnable{
    private final IShortUrlService  shortUrlService;
    private final String shortUrl;

    public ShortUrlHandler(IShortUrlService shortUrlService, String shortUrl) {
        this.shortUrlService = shortUrlService;
        this.shortUrl = shortUrl;
    }

    @Override
    public void run() {
        shortUrlService.addClickCount(shortUrl);
    }
}

4、ShortUrlController 控制器类

@RestController
@RequestMapping("/api/shortUrl")
@Api(tags = {"C端-短链接"})
@RefreshScope
public class ShortUrlController extends BaseController {
    @DubboReference
    private IShortUrlService shortUrlService;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    //short-utl:
    //    prefix: http://test.gateway.yaotiao.net/coupon-provider/api/shortUrl/redirect/
    @Value("${short-utl.prefix:https://gateway.jumituangou.com/coupon-provider/api/shortUrl/redirect/}")
    private String prefixUrl;

    @GetMapping("/generator")
    @ApiOperation("生成短链")
    public ResponseResult<String> shortUrlGenerator(ShortUrlRequest shortUrlRequest) {
        shortUrlRequest.setExpirationDate(DateUtil.getDateAfterDay(new Date(), 60));
        String shortUrl = prefixUrl + shortUrlService.shortUrlGenerator(shortUrlRequest);
        return success(shortUrl);
    }

    @GetMapping("/redirect/{shortUrl}")
    @ApiOperation("短链重定向")
    @IgnoreUrlsAnnon("/api/shortUrl/") //白名单
    public void redirect(@PathVariable String shortUrl, HttpServletResponse response) {
        String url = shortUrlService.getUrl(shortUrl);
        try {
            response.sendRedirect(url);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 异步增加短链点击次数
        ShortUrlHandler shortUrlHandler = new ShortUrlHandler(shortUrlService, shortUrl);
        threadPoolTaskExecutor.execute(shortUrlHandler);
    }
}

5、mysql 表 short_url;

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值