package com.junjie.shortlink.project.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.Week;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.junjie.shortlink.project.common.convention.exception.ClientException;
import com.junjie.shortlink.project.common.convention.exception.ServiceException;
import com.junjie.shortlink.project.common.enums.VailDateTypeEnum;
import com.junjie.shortlink.project.config.GotoDomainWhiteListConfiguration;
import com.junjie.shortlink.project.dao.entity.*;
import com.junjie.shortlink.project.dao.mapper.*;
import com.junjie.shortlink.project.dto.biz.ShortLinkStatsRecordDTO;
import com.junjie.shortlink.project.dto.req.ShortLinkBatchCreateReqDTO;
import com.junjie.shortlink.project.dto.req.ShortLinkCreateReqDTO;
import com.junjie.shortlink.project.dto.req.ShortLinkPageReqDTO;
import com.junjie.shortlink.project.dto.req.ShortLinkUpdateReqDTO;
import com.junjie.shortlink.project.dto.resp.*;
import com.junjie.shortlink.project.mq.producer.DelayShortLinkStatsProducer;
import com.junjie.shortlink.project.mq.producer.ShortLinkStatsSaveRedisProducer;
import com.junjie.shortlink.project.service.LinkStatsTodayService;
import com.junjie.shortlink.project.service.ShortLinkService;
import com.junjie.shortlink.project.toolkit.HashUtil;
import com.junjie.shortlink.project.toolkit.LinkUtil;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
import static com.junjie.shortlink.project.common.constant.RedisKeyConstant.*;
import static com.junjie.shortlink.project.common.constant.ShortLinkConstant.AMAP_REMOTE_URL;
/**
* 短链接接口实现层
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO> implements ShortLinkService {
private final RBloomFilter<String> shortUriCreateCachePenetrationBloomFilter;
private final ShortLinkGotoMapper shortLinkGotoMapper;
private final StringRedisTemplate stringRedisTemplate;
private final RedissonClient redissonClient;
private final LinkAccessStatsMapper linkAccessStatsMapper;
private final LinkLocaleStatsMapper linkLocaleStatsMapper;
private final LinkOsStatsMapper linkOsStatsMapper;
private final LinkBrowserStatsMapper linkBrowserStatsMapper;
private final LinkDeviceStatsMapper linkDeviceStatsMapper;
private final LinkAccessLogsMapper linkAccessLogsMapper;
private final LinkNetworkStatsMapper linkNetworkStatsMapper;
private final LinkStatsTodayMapper linkStatsTodayMapper;
private final LinkStatsTodayService linkStatsTodayService;
private final DelayShortLinkStatsProducer delayShortLinkStatsProducer;
private final GotoDomainWhiteListConfiguration gotoDomainWhiteListConfiguration;
private final ShortLinkStatsSaveRedisProducer shortLinkStatsSaveProducer;
@Value("${short-link.stats.locale.amap-key}")
private String statsLocaleAmapKey;
@Value("${short-link.domain.default}")
private String createShortLinkDefaultDomain;
@Transactional(rollbackFor = Exception.class)
@Override
public ShortLinkCreateRespDTO createShortLink(ShortLinkCreateReqDTO requestParam) {
// 校验原链接是否在白名单中
verificationWhitelist(requestParam.getOriginUrl());
// 生成短链接
String shortLinkSuffix = generatorSuffix(requestParam);
// 拼接成完整短链接
String fullShortUrl = StrBuilder.create(createShortLinkDefaultDomain)
.append("/")
.append(shortLinkSuffix)
.toString();
// 构建短链接对象
ShortLinkDO shortLinkDO = ShortLinkDO.builder()
.domain(createShortLinkDefaultDomain)
.originUrl(requestParam.getOriginUrl())
.gid(requestParam.getGid())
.createdType(requestParam.getCreatedType())
.validDateType(requestParam.getValidDateType())
.validDate(requestParam.getValidDate())
.describe(requestParam.getDescribe())
.shortUri(shortLinkSuffix)
.enableStatus(0)
.totalPv(0)
.totalUv(0)
.totalUip(0)
.delTime(0L)
.fullShortUrl(fullShortUrl)
.favicon(getFavicon(requestParam.getOriginUrl()))
.build();
// 路由表对象
ShortLinkGotoDO linkGotoDO = ShortLinkGotoDO.builder()
.fullShortUrl(fullShortUrl)
.gid(requestParam.getGid())
.build();
// 这里因为数据库设置了唯一索引,所以如果重复入库会报错
// 这里重复入库的情况就是布隆过滤器误判
try{
baseMapper.insert(shortLinkDO);
// 路由表插入对应路径
shortLinkGotoMapper.insert(linkGotoDO);
}catch (DuplicateKeyException ex){
// keypoint 误判的短链接如何处理 一般布隆过滤器满了误判概率才大,否则数据量小是很难误判的
// 第一种,短链接真实存在于缓存中
// 第二种,短链接不一定存在缓存中
// 这里我们发现重复了才抛异常
// LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
// .eq(ShortLinkDO::getFullShortUrl, fullShortUrl);
// ShortLinkDO hasShortLinkDO = baseMapper.selectOne(queryWrapper);
// if(hasShortLinkDO != null){
// log.warn("短链接:{} 重复入库",fullShortUrl);
// throw new ServiceException("短链接生成重复");
// }
// keypoint 这是我们相信布隆不存在则短链接一定不存在的情况下,不过高并发情况下也可能重复,但
// 是也没必要向上