12:修改link

8:修改短链接

前端传入的数据 - ShortLinkUpdateReqDTO
/**
 * 短链接修改请求对象
 */
@Data
public class ShortLinkUpdateReqDTO {

    /**
     * 原始链接
     */
    private String originUrl;

    /**
     * 完整短链接
     */
    private String fullShortUrl;

    /**
     * 原始分组标识
     */
    private String originGid;

    /**
     * 分组标识
     */
    private String gid;

    /**
     * 有效期类型 0:永久有效 1:自定义
     */
    private Integer validDateType;

    /**
     * 有效期
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date validDate;

    /**
     * 描述
     */
    private String describe;
}
controller
    /**
     * 修改短链接
     */
    @PostMapping("/api/short-link/v1/update")
    public Result<Void> updateShortLink(@RequestBody ShortLinkUpdateReqDTO requestParam) {
        shortLinkService.updateShortLink(requestParam);
        return Results.success();
    }
ShortLinkServiceImpl
@Transactional(rollbackFor = Exception.class)
    @Override
    public void updateShortLink(ShortLinkUpdateReqDTO requestParam) {
        verificationWhitelist(requestParam.getOriginUrl());
        LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
                .eq(ShortLinkDO::getGid, requestParam.getOriginGid())
                .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                .eq(ShortLinkDO::getDelFlag, 0)
                .eq(ShortLinkDO::getEnableStatus, 0);
        ShortLinkDO hasShortLinkDO = baseMapper.selectOne(queryWrapper);//根据当前完整短链接以及原始gid查询数据库


        if (hasShortLinkDO == null) { //如果没有查到,说明短链接被删除了,或者是说传进来的参数有误
            throw new ClientException("短链接记录不存在");
        }


        if (Objects.equals(hasShortLinkDO.getGid(), requestParam.getGid())) { //传入的gid与想要修改的gid是否一致,如果一致,说明没要求对当前的短链接进行换组
            LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
                    .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                    .eq(ShortLinkDO::getGid, requestParam.getGid())
                    .eq(ShortLinkDO::getDelFlag, 0)
                    .eq(ShortLinkDO::getEnableStatus, 0)
                    .set(Objects.equals(requestParam.getValidDateType(), VailDateTypeEnum.PERMANENT.getType()), ShortLinkDO::getValidDate, null); //检查一下是否设置为永久有效,如果是永久有效的化,就将其设置为null,如果不是的化,就设置为想要设置的有效时间


            ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                    .domain(hasShortLinkDO.getDomain())
                    .shortUri(hasShortLinkDO.getShortUri())
                    .favicon(hasShortLinkDO.getFavicon())
                    .createdType(hasShortLinkDO.getCreatedType())
                    .gid(requestParam.getGid())
                    .originUrl(requestParam.getOriginUrl())
                    .describe(requestParam.getDescribe())
                    .validDateType(requestParam.getValidDateType())
                    .validDate(requestParam.getValidDate())
                    .build();
            baseMapper.update(shortLinkDO, updateWrapper); //对要更新的内容进行更新


        } else {
            // 为什么监控表要加上Gid?不加的话是否就不存在读写锁?详情查看:https://nageoffer.com/shortlink/question
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(String.format(LOCK_GID_UPDATE_KEY, requestParam.getFullShortUrl())); //创建一个读写锁对象,锁键为:LOCK_GID_UPDATE_KEY, requestParam.getFullShortUrl()
            RLock wLock = readWriteLock.writeLock();//写锁对象
            wLock.lock();//尝试获取写锁,如果写锁被其他线程正在使用着,那么当前线程会进入阻塞状态,知道锁释放
            try {
                LambdaUpdateWrapper<ShortLinkDO> linkUpdateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
                        .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                        .eq(ShortLinkDO::getGid, hasShortLinkDO.getGid())
                        .eq(ShortLinkDO::getDelFlag, 0)//未被删除
                        .eq(ShortLinkDO::getDelTime, 0L)//表示删除时间为0
                        .eq(ShortLinkDO::getEnableStatus, 0);
                ShortLinkDO delShortLinkDO = ShortLinkDO.builder()
                        .delTime(System.currentTimeMillis())
                        .build();
                delShortLinkDO.setDelFlag(1);
                baseMapper.update(delShortLinkDO, linkUpdateWrapper); //在 GID 发生变化时,先将旧的短链接记录标记为删除(逻辑删除),以便后续插入新的记录。



                ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                        .domain(createShortLinkDefaultDomain)
                        .originUrl(requestParam.getOriginUrl())
                        .gid(requestParam.getGid())
                        .createdType(hasShortLinkDO.getCreatedType())
                        .validDateType(requestParam.getValidDateType())
                        .validDate(requestParam.getValidDate())
                        .describe(requestParam.getDescribe())
                        .shortUri(hasShortLinkDO.getShortUri())
                        .enableStatus(hasShortLinkDO.getEnableStatus())
                        .totalPv(hasShortLinkDO.getTotalPv())
                        .totalUv(hasShortLinkDO.getTotalUv())
                        .totalUip(hasShortLinkDO.getTotalUip())
                        .fullShortUrl(hasShortLinkDO.getFullShortUrl())
                        .favicon(getFavicon(requestParam.getOriginUrl()))
                        .delTime(0L)
                        .build();
                baseMapper.insert(shortLinkDO);//插入新的gid


                LambdaQueryWrapper<ShortLinkGotoDO> linkGotoQueryWrapper = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
                        .eq(ShortLinkGotoDO::getFullShortUrl, requestParam.getFullShortUrl())
                        .eq(ShortLinkGotoDO::getGid, hasShortLinkDO.getGid());
                ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(linkGotoQueryWrapper);
                shortLinkGotoMapper.delete(linkGotoQueryWrapper);
                shortLinkGotoDO.setGid(requestParam.getGid());
                shortLinkGotoMapper.insert(shortLinkGotoDO);
            } finally {
                wLock.unlock();
            }
        }
        // 短链接如何保障缓存和数据库一致性?详情查看:https://nageoffer.com/shortlink/question
        if (!Objects.equals(hasShortLinkDO.getValidDateType(), requestParam.getValidDateType())
                || !Objects.equals(hasShortLinkDO.getValidDate(), requestParam.getValidDate())
                || !Objects.equals(hasShortLinkDO.getOriginUrl(), requestParam.getOriginUrl())) {
            stringRedisTemplate.delete(String.format(GOTO_SHORT_LINK_KEY, requestParam.getFullShortUrl()));
            Date currentDate = new Date();
            if (hasShortLinkDO.getValidDate() != null && hasShortLinkDO.getValidDate().before(currentDate)) {
                if (Objects.equals(requestParam.getValidDateType(), VailDateTypeEnum.PERMANENT.getType()) || requestParam.getValidDate().after(currentDate)) {
                    stringRedisTemplate.delete(String.format(GOTO_IS_NULL_SHORT_LINK_KEY, requestParam.getFullShortUrl()));
                }
            }
        }
    }

主要的代码逻辑就是,当用户要修改短链接的时候,可能会将其分组也改了,所以这里就要加一层判断。

当分组没有改的时候,直接进行更新,然后看一下设置的短链接有效期时间是否是永久,如果是的化,就在数据库内设置为null

如果分组确实改了,那就先删除数据库中旧的短链接数据,然后添加新的分组短链接数据。

因为这里涉及到先删除后插入,所以使用分布式锁- 读写锁,确保 逻辑删除和插入操作的原子性,或脏读幻读

(1):并发插入问题:导致同一个短链接在数据库中不同的分组下出现多次

线程1:开始执行短链接更新操作。

  • 标记旧的短链接记录为“已删除”。
  • 尚未插入新记录。

线程2:同时开始处理相同的短链接更新操作。

  • 线程2在线程1插入新记录之前,读取到了“已删除”的旧记录,并开始进行更新。因为此时线程1的事务还没有提交,getDelFlag = 0
  • 最终,线程2可能也会插入一条新记录。

结果:数据库中可能会出现两条记录,一个是线程1插入的,另一个是线程2插入的。这会导致数据重复和不一致。

**解决措施:**加锁

(2):不完整事务问题

线程1:正在处理短链接更新操作。

  • 标记旧记录为“已删除”。
  • 尚未插入新记录。

线程2:在此时开始读取数据,读取到的是“已删除”的旧记录,并基于这个状态执行某些业务逻辑。

  • 由于读取到了一个“不完整”的中间状态数据,线程2可能会执行一些不合适的操作,导致数据不一致或逻辑错误。例如展示当前用户有哪些短链接的时候,就会出现漏读的问题。

结果:业务逻辑可能被错误的数据状态所影响,造成进一步的问题。

**解决措施:**开启事务模式

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HackerTerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值