SaaS短链接系统-新手从零学习 7 – 回收站管理

eve2333 发布于 24 天前 28 次阅读


 - 第01节:回收站模块功能分析

基本功能模块如下:

  1. 将短链接移至回收站;
  2. 回收站分页列表查询;
  3. 短链接从回收站恢复;
  4. 短链接从回收站删除。

- 第02节:短链接移至回收站功能

注意del_flag 控制是否真的删除,逻辑删除,实现回收站的删除和恢复只需要更改这个字段就行了

创建回收站也不用创建什么数据表,把短链接的那个状态修改一下flag里面,在project里面创建如下controller层

package com.nageoffer.shortlink.project.controller;
import com.nageoffer.shortlink.project.common.convention.result.Result;
import com.nageoffer.shortlink.project.common.convention.result.Results;
import com.nageoffer.shortlink.project.dto.req.RecycleBinSaveReqDTO;
import com.nageoffer.shortlink.project.service.RecycleBinService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 回收站管理控制层
 */
@RestController
@RequiredArgsConstructor
public class RecycleBinController {

    private final RecycleBinService recycleBinService;

    /**
     * 保存回收站
     */
    @PostMapping("/api/short-link/v1/recycle-bin/save")
    public Result<Void> saveRecycleBin(@RequestBody RecycleBinSaveReqDTO requestParam) {
        recycleBinService.saveRecycleBin(requestParam);
        return Results.success();
    }
}

 在Req里面保存功能

package com.nageoffer.shortlink.project.dto.req;


import lombok.Data;

/**
 * 回收站保存功能
 */
@Data
public class RecycleBinSaveReqDTO {

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

    /**
     * 全部短链接
     */
    private String fullShortUrl;
}

Service里面创建service 

package com.nageoffer.shortlink.project.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.nageoffer.shortlink.project.dao.entiry.ShortLinkDO;
import com.nageoffer.shortlink.project.dto.req.RecycleBinSaveReqDTO;

/**
 * 回收站管理接口层
 */
public interface RecycleBinService extends IService<ShortLinkDO> {

    /**
     * 保存回收站
     *
     * @param requestParam 请求参数
     */
    void saveRecycleBin(RecycleBinSaveReqDTO requestParam);
}
package com.nageoffer.shortlink.project.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nageoffer.shortlink.project.dao.entiry.ShortLinkDO;
import com.nageoffer.shortlink.project.dao.mapper.ShortLinkMapper;
import com.nageoffer.shortlink.project.dto.req.RecycleBinSaveReqDTO;
import com.nageoffer.shortlink.project.service.RecycleBinService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import static com.nageoffer.shortlink.project.common.constant.RedisKeyConstant.GOTO_SHORT_LINK_KEY;

/**
 * 回收站管理接口实现层
 */
@Service
@RequiredArgsConstructor
public class RecycleBinServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO> implements RecycleBinService {

    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public void saveRecycleBin(RecycleBinSaveReqDTO requestParam) {
        LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
                .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                .eq(ShortLinkDO::getGid, requestParam.getGid())
                .eq(ShortLinkDO::getEnableStatus, 0)
                .eq(ShortLinkDO::getDelFlag, 0);
        ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                .enableStatus(1)
                .build();
        baseMapper.update(shortLinkDO, updateWrapper);
        stringRedisTemplate.delete(String.format(GOTO_SHORT_LINK_KEY, requestParam.getFullShortUrl()));
    }
}

 我们在admin也要实现一下,RemoteService的部分如下:

/**
 * 保存回收站
 *
 * @param requestParam 请求参数
 */
default void saveRecycleBin(RecycleBinSaveReqDTO requestParam) {
    HttpUtil.post("http://127.0.0.1:8001/api/short-link/v1/recycle-bin/save", JSON.toJSONString(requestParam));
}

 新建controller层

package com.nageoffer.shortlink.admin.controller;

import com.nageoffer.shortlink.admin.common.convention.result.Result;
import com.nageoffer.shortlink.admin.common.convention.result.Results;
import com.nageoffer.shortlink.admin.dto.req.RecycleBinSaveReqDTO;
import com.nageoffer.shortlink.admin.remote.ShortLinkRemoteService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 回收站管理控制层
 */
@RestController
@RequiredArgsConstructor
public class RecycleBinController {

    /**
     * 后续重构为 SpringCloud Feign 调用
     */
    ShortLinkRemoteService shortLinkRemoteService = new ShortLinkRemoteService() {
    };

    /**
     * 保存回收站
     */
    @PostMapping("/api/short-link/v1/recycle-bin/save")
    public Result<Void> saveRecycleBin(@RequestBody RecycleBinSaveReqDTO requestParam) {
        shortLinkRemoteService.saveRecycleBin(requestParam);
        return Results.success();
    }
}

 我们移到回收站了,那么这个短链接就不能再次被访问了。一个要去掉一个短链接的那个impl里面,看保存的那些缓存,把他的缓存删除。

        stringRedisTemplate.delete(String.format(GOTO_SHORT_LINK_KEY, requestParam.getFullShortUrl()));

现在你还是可以访问的对吧,我们删除它

{
    "gid": "1k91Uw",
    "fullShortUrl": "nurl.ink/4bLMoL"
}

 会报大概一个错误

移至回收站之后,再次判断时,应该这样写: if (shortLinkDO == null || (shortLinkDO.getValidDate() != null && shortLinkDO.getValidDate().before(new Date())){}不然不设置有效期的话,会空指针

这个好像是在缓存里删除了,sql里面del_flag不知道为什么没变成1 

神经蛙:if (shortLinkDO == null || (shortLinkDO.getValidDate() != null && shortLinkDO.getValidDate().before(new Date()))) {这里要这样改,shortLinkDO.getValidDate() != null不能丢,不然的话可能会报NullPointerException

2025-03-06 20:29

学不会的小王 回复 神经蛙:哥们我不太懂,这个如果shortLinkDO不为空的话,这个短链接不是一定有有效期吗,这个有效期不是在创建短链接的时候就有的吗

2025-03-19 21:48

神经蛙 回复 学不会的小王:时间太久了,有点忘了,我刚回去看了一下,就是两种情况嘛,第一种就是查询数据库查询不到,这样就是有可能是恶意用户,我们直接在缓存中放一个空的缓存就OK了,第二种就是在数据库中查询到了并且是一个自定义有效期的,但是已经过期了的情况,我们也是直接给他一个空缓存,直接跳转找不到页面的情况

2025-03-19 22:14

涿野夕凉丶 回复 学不会的小王:记得前面有说吧,永久有效的短链,有效期validDate设置为null

2025-04-12 10:59

神经蛙:我擦了,xdm,有个问题给兄弟们提示一下就是这个在回收站里面删除缓存以及这个在这个重定向这里获取这个缓存的时候这里,也就是像是这个视频最后作者遇到的问题一样就是,回收站这里删过后就是,到了重定向这里进行获取的时候依然是能够进行获取到,并且就是回收站这里进行更新操作这里,没有任何报错,但是更新不了数据库,这个,我擦,我调试了一会总是没有报错但是就像是没有生效一样,浪费了好长时间,对了好几遍代码依然是没有发现是哪里错了,崩溃了,,,,,,,没想到最后突然发现就是我这里在进行重定向的时候就是是带有http的,这个不重要,重要的是回收站删除缓存哪里,我穿的fullshortUrl也是带有http的,这样就导致我在回收站删的redis key 是是带有http的,但是重定向这里删除的redis key是不带有key值的

 布隆过滤器不能删除这个问题可以解决吗,或者说是需要解决吗。我的问题是,如果一个短链接已经移入了回收站,但是布隆过滤器中还是会判断它存在,然后继续走判断缓存或者是查询数据库的逻辑。要是能直接判断为不存在就好了

弘历 回复 cmqqq:已经删除的短链接我们可以加一层 Set 缓存,彻底删除的数据可以加入到这个 Set 集合中。如果判断在布隆过滤器中存在,需要再去判断是否在 Set 集合,如果存在就证明短链接可用。

基洛夫 回复 弘历:请问直接把它加在空key里可以吗

。。。W 回复 cmqqq:我认为不用删除布隆过滤器的数据,可以定时创建新的布隆过滤器,然后新增数据只往新的过滤器添加,判断数据就新旧过滤器一起判断,然后依照自己系统的情况,定时把最旧的过滤器移除。

Xknnz 回复 cmqqq:移入回收站,他只是暂时不可用,只有真正在回收站里删除的话,才需要删布隆吧,因为移入回收站的短链接,也可以再恢复可用的吧

将短链接放到垃圾回收器中,添加了一个将短链接放到缓存的不存在表中,后续就不需要查数据库了,直接走不存在列表 @Override public void save(RecycleBinSaveReqDTO requestParam) { LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class) .eq(ShortLinkDO::getGid, requestParam.getGid()) .eq(ShortLinkDO::getFullUrl, requestParam.getFullShortUrl()) .eq(ShortLinkDO::getDelFlag, 0) .eq(ShortLinkDO::getEnableStatus, 0); ShortLinkDO shortLinkDO = ShortLinkDO.builder() .enableStatus(1) .build(); baseMapper.update(shortLinkDO, updateWrapper); // 把短链接从缓存中删除 redisTemplate.delete(String.format(RedisKeyConstant.GOTO_SHORT_LINK_KEY, requestParam.getFullShortUrl())); // 把短链接放到不存在列表 redisTemplate.opsForValue() .set(String.format(RedisKeyConstant.GOTO_IS_NULL_SHORT_LINK_KEY, requestParam.getFullShortUrl()), "-", 30, TimeUnit.SECONDS); } 

- 第03节:回收站分页查询功能(上)

直接复制到admin的controller里面RecycleBinController.java

/**
 * 分页查询回收站短链接
 */
@GetMapping("/api/short-link/admin/v1/recycle-bin/page")
public Result<IPage<ShortLinkPageRespDTO>> pageShortLink(ShortLinkPageReqDTO requestParam) {
    return shortLinkRemoteService.pageRecycleBinShortLink(requestParam);
}

 RecycleBinServicelmpl.java里面复制

@Override
public IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkPageReqDTO requestParam) {
    LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
            .eq(ShortLinkDO::getGid, requestParam.getGid())
            .eq(ShortLinkDO::getEnableStatus, 1)
            .eq(ShortLinkDO::getDelFlag, 0)
            .orderByDesc(ShortLinkDO::getCreateTime);
    IPage<ShortLinkDO> resultPage = baseMapper.selectPage(requestParam, queryWrapper);
    return resultPage.convert(each -> {
        ShortLinkPageRespDTO result = BeanUtil.toBean(each, ShortLinkPageRespDTO.class);
        result.setDomain("http://" + result.getDomain());
        return result;
    });
}

 project的controller加一个

/**
 * 分页查询回收站短链接
 */
@GetMapping("/api/short-link/v1/recycle-bin/page")
public Result<IPage<ShortLinkPageRespDTO>> pageShortLink(ShortLinkPageReqDTO requestParam) {
    return Results.success(recycleBinService.pageShortLink(requestParam));
}

 shortlinkremoteservice

/**
 * 分页查询回收站短链接
 *
 * @param requestParam 分页短链接请求参数
 * @return 查询短链接响应
 */
default Result<IPage<ShortLinkPageRespDTO>> pageRecycleBinShortLink(ShortLinkPageReqDTO requestParam) {
    Map<String, Object> requestMap = new HashMap<>();
    requestMap.put("gid", requestParam.getGid());
    requestMap.put("current", requestParam.getCurrent());
    requestMap.put("size", requestParam.getSize());
    String resultPageStr = HttpUtil.get("http://127.0.0.1:8001/api/short-link/v1/recycle-bin/page", requestMap);
    return JSON.parseObject(resultPageStr, new TypeReference<>() {
    });
}

 service层自行补充

/**
 * 分页查询短链接
 *
 * @param requestParam 分页查询短链接请求参数
 * @return 短链接分页返回结果
 */
IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkPageReqDTO requestParam);

feature:开发短链接分页查询回收站功能 

这里传入了gid,那么查询到的只是这个分组下enableStatus=1的短链接,而我们的回收站中应该是包含所有分组中enableStatus=1的短链接.(不过这个问题下节视频就给解决了)。

- 第04节:回收站分页查询功能(下)

 关于我们这个回收站查询的分页,其实是有问题的:如果说我们将分页的这个回收站查询也要加上分组的话,其实这样就非常不利于检索;所以说我们这里直接把分组去掉,我们在这个呢中台这边去给他去做这种隐式的分组传递;

我们在那个shortlink那张表里面,我们是用的这个分组就是GID去做的这种分表,如果说不传分组的话默认查全表,所以这里的话我们就只能够通过一种方法去解决,就是查询当前这个用户下的所有分组,相当于我们去干什么,我们去把分组传过去,在相当于在我们的库管这里去查询之前,真正的去调用之前,我们要把分组的 list去给他干什么?去给他全部查出来,然后你把你的用户下面所有分组全部查到,然后赋值给这里,然后在我们的平台那边直接就查所有就好了。首先在这里我们去给他找到一个,然后不是在这。然后在这里的话,我看一下这里能不能住进。老师这里我说的应该不太对,ok在这里我想能不能通过这里再住进来, interface可能够呛。我们这边的话可能需要一个方法,我想方法的话,我们这里看来还得创建一个service,要回收站,对吧?结果是这种。不好意思。然后我们这边去给他做一个转发,我们在这里面把这个方法复制过来,这边的话我们都复制过来了

 加一个admin/ 和

 @RestController
@RequiredArgsConstructor
public class RecycleBinController {

    /**
     * 后续重构为 SpringCloud Feign 调用
     */
    ShortLinkRemoteService shortLinkRemoteService = new ShortLinkRemoteService() {
    };
    private final RecycleBinService recycleBinService;

    /**
     * 保存回收站
     */
    @PostMapping("/api/short-link/admin/v1/recycle-bin/save")
    public Result<Void> saveRecycleBin(@RequestBody RecycleBinSaveReqDTO requestParam) {
        shortLinkRemoteService.saveRecycleBin(requestParam);
        return Results.success();
    }

    /**
     * 分页查询回收站短链接
     */
    @GetMapping("/api/short-link/admin/v1/recycle-bin/page")
    public Result<IPage<ShortLinkPageRespDTO>> pageShortLink(ShortLinkRecycleBinPageReqDTO requestParam) {
        return recycleBinService.pageRecycleBinShortLink(requestParam);
    }

新建一个req, 

/**
 * 分页查询回收站短链接
 */
@GetMapping("/api/short-link/admin/v1/recycle-bin/page")
public Result<IPage<ShortLinkPageRespDTO>> pageShortLink(ShortLinkRecycleBinPageReqDTO requestParam) {
    return recycleBinService.pageRecycleBinShortLink(requestParam);
}

 新建service,在admin中新建admin,

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.nageoffer.shortlink.admin.common.convention.result.Result;
import com.nageoffer.shortlink.admin.remote.dto.req.ShortLinkRecycleBinPageReqDTO;
import com.nageoffer.shortlink.admin.remote.dto.resp.ShortLinkPageRespDTO;

/**
 * URL 回收站接口层
 */
public interface RecycleBinService {

    /**
     * 分页查询回收站短链接
     *
     * @param requestParam 请求参数
     * @return 返回参数包装
     */
    Result<IPage<ShortLinkPageRespDTO>> pageRecycleBinShortLink(ShortLinkRecycleBinPageReqDTO requestParam);
}

 admin的impl实现,

package com.nageoffer.shortlink.admin.service.impl;


import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.nageoffer.shortlink.admin.common.biz.user.UserContext;
import com.nageoffer.shortlink.admin.common.convention.exception.ServiceException;
import com.nageoffer.shortlink.admin.common.convention.result.Result;
import com.nageoffer.shortlink.admin.dao.entiry.GroupDO;
import com.nageoffer.shortlink.admin.dao.mapper.GroupMapper;
import com.nageoffer.shortlink.admin.remote.ShortLinkRemoteService;
import com.nageoffer.shortlink.admin.remote.dto.req.ShortLinkRecycleBinPageReqDTO;
import com.nageoffer.shortlink.admin.remote.dto.resp.ShortLinkPageRespDTO;
import com.nageoffer.shortlink.admin.service.RecycleBinService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * URL 回收站接口实现层
 */
@Service
@RequiredArgsConstructor
public class RecycleBinServiceImpl implements RecycleBinService {

    private final GroupMapper groupMapper;

    /**
     * 后续重构为 SpringCloud Feign 调用
     */
    ShortLinkRemoteService shortLinkRemoteService = new ShortLinkRemoteService() {
    };

    @Override
    public Result<IPage<ShortLinkPageRespDTO>> pageRecycleBinShortLink(ShortLinkRecycleBinPageReqDTO requestParam) {
        LambdaQueryWrapper<GroupDO> queryWrapper = Wrappers.lambdaQuery(GroupDO.class)
                .eq(GroupDO::getUsername, UserContext.getUsername())
                .eq(GroupDO::getDelFlag, 0);
        List<GroupDO> groupDOList = groupMapper.selectList(queryWrapper);
        if (CollUtil.isEmpty(groupDOList)) {
            throw new ServiceException("用户无分组信息");
        }
        requestParam.setGidList(groupDOList.stream().map(GroupDO::getGid).toList());
        return shortLinkRemoteService.pageRecycleBinShortLink(requestParam);
    }
}

 ShortLinkRemoteService.java修改如下,

/**
 * 分页查询回收站短链接
 *
 * @param requestParam 分页短链接请求参数
 * @return 查询短链接响应
 */
default Result<IPage<ShortLinkPageRespDTO>> pageRecycleBinShortLink(ShortLinkRecycleBinPageReqDTO requestParam) {
    Map<String, Object> requestMap = new HashMap<>();
    requestMap.put("gidList", requestParam.getGidList());
    requestMap.put("current", requestParam.getCurrent());
    requestMap.put("size", requestParam.getSize());
    String resultPageStr = HttpUtil.get("http://127.0.0.1:8001/api/short-link/v1/recycle-bin/page", requestMap);
    return JSON.parseObject(resultPageStr, new TypeReference<>() {
    });
}

 project controller

package com.nageoffer.shortlink.project.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.nageoffer.shortlink.project.common.convention.result.Result;
import com.nageoffer.shortlink.project.common.convention.result.Results;
import com.nageoffer.shortlink.project.dto.req.RecycleBinSaveReqDTO;
import com.nageoffer.shortlink.project.dto.req.ShortLinkPageReqDTO;
import com.nageoffer.shortlink.project.dto.req.ShortLinkRecycleBinPageReqDTO;
import com.nageoffer.shortlink.project.dto.resp.ShortLinkPageRespDTO;
import com.nageoffer.shortlink.project.service.RecycleBinService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 回收站管理控制层
 */
@RestController
@RequiredArgsConstructor
public class RecycleBinController {

    private final RecycleBinService recycleBinService;

    /**
     * 保存回收站
     */
    @PostMapping("/api/short-link/v1/recycle-bin/save")
    public Result<Void> saveRecycleBin(@RequestBody RecycleBinSaveReqDTO requestParam) {
        recycleBinService.saveRecycleBin(requestParam);
        return Results.success();
    }

    /**
     * 分页查询回收站短链接
     */
    @GetMapping("/api/short-link/v1/recycle-bin/page")
    public Result<IPage<ShortLinkPageRespDTO>> pageShortLink(ShortLinkRecycleBinPageReqDTO requestParam) {
        return Results.success(recycleBinService.pageShortLink(requestParam));
    }
}

project的 ShortLinkRecycleBinPageRegDTO.java

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nageoffer.shortlink.project.dao.entiry.ShortLinkDO;
import lombok.Data;

import java.util.List;

/**
 * 回收站短链接分页请求参数
 */
@Data
public class ShortLinkRecycleBinPageReqDTO extends Page<ShortLinkDO> {

    /**
     * 分组标识
     */
    private List<String> gidList;
}

 RecycleBinService.java

package com.nageoffer.shortlink.project.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nageoffer.shortlink.project.dao.entiry.ShortLinkDO;
import com.nageoffer.shortlink.project.dto.req.RecycleBinSaveReqDTO;
import com.nageoffer.shortlink.project.dto.req.ShortLinkPageReqDTO;
import com.nageoffer.shortlink.project.dto.req.ShortLinkRecycleBinPageReqDTO;
import com.nageoffer.shortlink.project.dto.resp.ShortLinkPageRespDTO;

/**
 * 回收站管理接口层
 */
public interface RecycleBinService extends IService<ShortLinkDO> {

    /**
     * 保存回收站
     *
     * @param requestParam 请求参数
     */
    void saveRecycleBin(RecycleBinSaveReqDTO requestParam);

    /**
     * 分页查询短链接
     *
     * @param requestParam 分页查询短链接请求参数
     * @return 短链接分页返回结果
     */
    IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkRecycleBinPageReqDTO requestParam);
}

project\...\RecycleBinServicelmpl.java 

@Override
public IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkRecycleBinPageReqDTO requestParam) {
    LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
            .in(ShortLinkDO::getGid, requestParam.getGidList())
            .eq(ShortLinkDO::getEnableStatus, 1)
            .eq(ShortLinkDO::getDelFlag, 0)
            .orderByDesc(ShortLinkDO::getUpdateTime);
    IPage<ShortLinkDO> resultPage = baseMapper.selectPage(requestParam, queryWrapper);
    return resultPage.convert(each -> {
        ShortLinkPageRespDTO result = BeanUtil.toBean(each, ShortLinkPageRespDTO.class);
        result.setDomain("http://" + result.getDomain());
        return result;
    });
}

 remote\..\ShortLinkRecycleBinPageReqDTO.java

package com.nageoffer.shortlink.admin.remote.dto.req;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;

import java.util.List;

/**
 * 回收站短链接分页请求参数
 */
@Data
public class ShortLinkRecycleBinPageReqDTO extends Page {

    /**
     * 分组标识
     */
    private List<String> gidList;
}

 进行回收站分页查询时,列表查询用户下的短链接分组发现只有默认分组。CodeReview发现,创建短链接传递的 gid 是前端传来的,并没有确定当前 gid 是属于当前用户下的一个分组,或者将该 gid 绑定到该用户。所以创建的短链接在 t_link 拥有 gid ,但是在t_group 中没有发现该 gid 的分组,即有一个 gid 拥有多个ShortLink,但是这个 gid 所代表的分组并没有和当前用户绑定,导致之前测试功能时无论是创建的短链接或者是移至回收站的短链接都没有属于某个用户。 这个问题将发生在该种情况:你创建短链接(ShortLink)时,没有使用用户(User)新增的短链接分组(Group)的Gid。 你在插入短链接的时候用的gid不是自己所创建的分组也是可以的,毕竟在创建短链接中没有调用GroupService进行判断是否gid属于该用户。

 之前是 admin 项目 Controller 层直接调用 Remote,因为需要查询用户所有分组,加了个 Service 中间层,改成 Controller 调用 Service,Service 调用 Remote,

相当于前端啥也没传么,这个gidList是根据当前用户查的数据库;这个admin里加了个service中间层,里面还有一个用于远程调用中台的service,可能因为你是边想边写,一直在来回修改,所以看着显得有点乱。不过自己慢慢捋捋也能明白:类似于上面说的 Admin 内部 Service 调用 Remote Service 在企业中挺常见的,毕竟不是所有请求都能透传,而是需要做一些处理。后边开新项目会注意这个事情

refactor:重构短链接回收站分页查询功能 

- 第05节:回收站恢复短链接功能

 project的controller

/**
 * 恢复短链接
 */
@PostMapping("/api/short-link/v1/recycle-bin/recover")
public Result<Void> recoverRecycleBin(@RequestBody RecycleBinRecoverReqDTO requestParam) {
    recycleBinService.recoverRecycleBin(requestParam);
    return Results.success();
}
package com.nageoffer.shortlink.project.dto.req;

import lombok.Data;

/**
 * 回收站恢复功能
 */
@Data
public class RecycleBinRecoverReqDTO {

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

    /**
     * 全部短链接
     */
    private String fullShortUrl;
}

 service中标一下

/**
 * 恢复短链接
 *
 * @param requestParam 请求参数
 */
void recoverRecycleBin(RecycleBinRecoverReqDTO requestParam);

 impl完成方法,只要set状态会成为0;之前我们相当于把短链接的缓存给删掉,现在一个是对短链接进行预热,还有就是把就是为了防止缓存穿透的空白的key,把这个删除

@Override
public void recoverRecycleBin(RecycleBinRecoverReqDTO requestParam) {
    LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
            .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
            .eq(ShortLinkDO::getGid, requestParam.getGid())
            .eq(ShortLinkDO::getEnableStatus, 1)
            .eq(ShortLinkDO::getDelFlag, 0);
    ShortLinkDO shortLinkDO = ShortLinkDO.builder()
            .enableStatus(0)
            .build();
    baseMapper.update(shortLinkDO, updateWrapper);
    stringRedisTemplate.delete(String.format(GOTO_IS_NULL_SHORT_LINK_KEY, requestParam.getFullShortUrl()));
}

其实这里可以不预热,因为一个咱们从产品功能上去分析,一个从回收站里面再拿出来的短链接不太可能成功

admin也要实现一些,controller层,remoteservice

/**
 * 恢复短链接
 */
@PostMapping("/api/short-link/admin/v1/recycle-bin/recover")
public Result<Void> recoverRecycleBin(@RequestBody RecycleBinRecoverReqDTO requestParam) {
    shortLinkRemoteService.recoverRecycleBin(requestParam);
    return Results.success();
}
/**
 * 恢复短链接
 *
 * @param requestParam 短链接恢复请求参数
 */
default void recoverRecycleBin(RecycleBinRecoverReqDTO requestParam) {
    HttpUtil.post("http://127.0.0.1:8001/api/short-link/v1/recycle-bin/recover", JSON.toJSONString(requestParam));
}

原来不是这个被放到回收站了,访问是notfound,现在我重新移出来,又可以访问了
{
    "gid": "1k91Uw",
    "fullShortUrl": "nurl.ink/4bLMoL"
}

 前面添加回收站的时候并没有把短链接添加到为空缓存,为什么这里恢复要删除为空缓存?

因为跳转功能逻辑:如果EnableStatus不为0的,那就增加一个新缓存,只要有这个缓存那直接跳到/page/notfound里面去,目的为了防止缓存穿透。但是之前放进回收站已经把EnableStatus设置为1了,所以是跳转功能功能就没了,但是你把恢复后,你不把那个缓存删掉。你怎么样都会失去跳转功能的。因为只要有这个缓存就永远的重定向到/page/notfound去。前面短链接重定向的时候为了解决缓存穿透问题添加了空缓存。如果这里不删除空缓存,后面用这个刚恢复的短链接时查到空缓存会被当作访问不存在的短链接而跳转404(而实际上这条短链接应该已经恢复正常使用)。所以要删除空缓存

这里如果回收站为空时会报错:主要问题在于SQL语句中出现了IN (),当传入的gidList为空时,导致语法错误。 
日志中记录的SQL是: ``` SELECT id, domain, ... FROM t_link WHERE (gid IN () AND enable_status = ? AND del_flag = ?) ORDER BY update_time DESC ``` 问题出在`IN ()`部分,当传入的参数为空集合时,SQL语句中的IN子句会变成`IN ()`,这在SQL语法中是不合法的,导致解析失败。我怀疑mb-p有点老了,但是版本我修改一下好像没有用

 project的impl尝试修改如下,但是日记狂查16表让我有点怕啊,没有按原来的

@Override
public IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkRecycleBinPageReqDTO requestParam) {
    LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)

            .eq(ShortLinkDO::getEnableStatus, 1)
            .eq(ShortLinkDO::getDelFlag, 0);
    //仅当 gidList 非空时添加 .in 条件
    if (CollectionUtils.isNotEmpty(requestParam.getGidList())) {
        queryWrapper.in(ShortLinkDO::getGid, requestParam.getGidList());
    }
    queryWrapper.orderByDesc(ShortLinkDO::getUpdateTime); // 将排序单独放最后

    IPage<ShortLinkDO> resultPage = baseMapper.selectPage(requestParam, queryWrapper);
    return resultPage.convert(each -> {
        ShortLinkPageRespDTO result = BeanUtil.toBean(each, ShortLinkPageRespDTO.class);
        result.setDomain("http://" + result.getDomain());
        return result;
    });
}

- 第06节:回收站移除短链接功能

 回收站删除短链接是直接baseMapper.delete()删除的数据库数据,那delFlag字段干啥用的?

这里是硬删除,可以参考12306的软删除配置;应该一开始想设计逻辑删除的,所以有delFlag字段,但这里给忘了,是真的删掉了。 真删除了,确实不好,数据对企业而言很有价值的。 不过逻辑删除这玩意也不好,数据量大了影响查询效率。更好的方式应该是迁移到一张删除表里面

马哥应该是忘了讲MP的逻辑删除配置了,因为配置了MP后可以直接这样使用delete实现逻辑删除 mybatis-plus: global-config: db-config: logic-delete-field: delFlag logic-delete-value: 1 logic-not-delete-value: 0 id-type: auto

 RecycleBinController.java 在admin\controller


    /**
     * 移除短链接
     */
    @PostMapping("/api/short-link/admin/v1/recycle-bin/remove")
    public Result<Void> removeRecycleBin(@RequestBody RecycleBinRemoveReqDTO requestParam) {
        shortLinkRemoteService.removeRecycleBin(requestParam);
        return Results.success();
    }

RecycleBinRemoveReqDTO.java  admin\dto\req

package com.nageoffer.shortlink.admin.dto.req;

import lombok.Data;

/**
 * 回收站移除功能
 */
@Data
public class RecycleBinRemoveReqDTO {

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

    /**
     * 全部短链接
     */
    private String fullShortUrl;
}

ShortLinkRemoteService.java

    /**
     * 移除短链接
     *
     * @param requestParam 短链接移除请求参数
     */
    default void removeRecycleBin(RecycleBinRemoveReqDTO requestParam) {
        HttpUtil.post("http://127.0.0.1:8001/api/short-link/v1/recycle-bin/remove", JSON.toJSONString(requestParam));
    }

RecycleBinController.java的project\controller

    /**
     * 移除短链接
     */
    @PostMapping("/api/short-link/v1/recycle-bin/remove")
    public Result<Void> removeRecycleBin(@RequestBody RecycleBinRemoveReqDTO requestParam) {
        recycleBinService.removeRecycleBin(requestParam);
        return Results.success();
    }

RecycleBinRemoveReqDTO在project\dto\req

package com.nageoffer.shortlink.project.dto.req;

import lombok.Data;

/**
 * 回收站移除功能
 */
@Data
public class RecycleBinRemoveReqDTO {

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

    /**
     * 全部短链接
     */
    private String fullShortUrl;
}

RecycleBinService.java  project\service 

package com.nageoffer.shortlink.project.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nageoffer.shortlink.project.dao.entity.ShortLinkDO;
import com.nageoffer.shortlink.project.dto.req.RecycleBinRecoverReqDTO;
import com.nageoffer.shortlink.project.dto.req.RecycleBinRemoveReqDTO;
import com.nageoffer.shortlink.project.dto.req.RecycleBinSaveReqDTO;
import com.nageoffer.shortlink.project.dto.req.ShortLinkRecycleBinPageReqDTO;
import com.nageoffer.shortlink.project.dto.resp.ShortLinkPageRespDTO;

/**
 * 回收站管理接口层
 */
public interface RecycleBinService extends IService<ShortLinkDO> {

    /**
     * 保存回收站
     *
     * @param requestParam 请求参数
     */
    void saveRecycleBin(RecycleBinSaveReqDTO requestParam);

    /**
     * 分页查询短链接
     *
     * @param requestParam 分页查询短链接请求参数
     * @return 短链接分页返回结果
     */
    IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkRecycleBinPageReqDTO requestParam);

    /**
     * 从回收站恢复短链接
     *
     * @param requestParam 恢复短链接请求参数
     */
    void recoverRecycleBin(RecycleBinRecoverReqDTO requestParam);

    /**
     * 从回收站移除短链接
     *
     * @param requestParam 移除短链接请求参数
     */
    void removeRecycleBin(RecycleBinRemoveReqDTO requestParam);
}

RecycleBinServicelmpl.java在project

    @Override
    public void removeRecycleBin(RecycleBinRemoveReqDTO requestParam) {
        LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
                .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                .eq(ShortLinkDO::getGid, requestParam.getGid())
                .eq(ShortLinkDO::getEnableStatus, 1)
                .eq(ShortLinkDO::getDelFlag, 0);
        baseMapper.delete(updateWrapper);
    }

以下仅供测试,在yaml中添加

#配置mybatis-plus软删除
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: delFlag
      logic-delete-value: 1
      logic-not-delete-value: 0
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

可以选择ShortLinkDO实体类字段加上 @TableLogic 注解: 

@Data
public class ShortLinkDO {

    // ...其他字段...

    @TableLogic(value = "0", delval = "1")
    private Integer delFlag;
}

将原本的硬删除(baseMapper.delete())改为使用 MyBatis-Plus 的删除方法(自动触发软删除)

 直接调用 baseMapper.deleteById(id)baseMapper.delete() 也会自动触发软删除。  

如图片所示:del_flag是1,已经删除了  ​