08. Use Redis to implement the check-in function

learning target:

Source: Dark Horse Tutorial
Using the BitMap data structure in Redis to use the check-in function and the continuous check-in function


Learning output:

Please add image description

solution:

1. 准备pom环境

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.23.1</version>
        </dependency>

2. 配置ThreadLocal和过滤器

public class UserHolder {
    
    
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
    
    
        tl.set(user);
    }

    public static UserDTO getUser(){
    
    
        return tl.get();
    }

    public static void removeUser(){
    
    
        tl.remove();
    }
}
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    
    
    @Autowired
    private StringRedisTemplate redis;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/voucher/**").order(2);
        registry.addInterceptor(new RefreshTokenInterceptor(redis)).addPathPatterns("/**").order(1);
    }
}
---------------------------------------------
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    
    

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //1.判断是否需要拦截ThreadLocal
        if (UserHolder.getUser()==null) {
    
    
            response.setStatus(401);
            return false;
        }
        //7.放行
        return true;
    }
    //渲染后返回给前台数据前
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        //移除用户,避免内存泄露
        UserHolder.removeUser();
    }
}
---------------------------------------------------
@Slf4j
public class RefreshTokenInterceptor implements HandlerInterceptor {
    
    
    //这个对象不是由spring管理的所以不能用注解自动注入

    private StringRedisTemplate redis;

    public RefreshTokenInterceptor(StringRedisTemplate redis) {
    
    
        this.redis = redis;
    }

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
    
    
            return true;
        }
        //2.基于token获取redis中的用户
        //通过key取到hash中的map集合数据
        Map<Object, Object> userMap = redis.opsForHash().entries("login:token:" + token);
        //3.判断用户是否存在
        if (userMap.isEmpty()) {
    
    
            return true;
        }
        //5.将查询到的hash数据转为userDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6.存在,保存用户信息到ThreadLocal中
        UserHolder.saveUser(userDTO);
        //7.刷新token有效期
        redis.expire(LOGIN_USER_KEY + token, 30, TimeUnit.MINUTES);
        log.info("我是第一个拦截器当前拦截所有请求的用户为,线程为{},{}",UserHolder.getUser(),Thread.currentThread());
        //8.放行
        return true;
    }

3. Controller层:负责接收请求和向下分配

@RestController
@RequestMapping("/user")
public class UserController{
    
    
	@Resource
	private IUserService userService;
    @PostMapping("/sign")
    public Result sign(){
    
    
        return userService.sign();
    }
}

4. Service层:负责业务的处理逻辑签到功能

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
    
    private static final ObjectMapper mapper = new ObjectMapper();
    @Resource
    private StringRedisTemplate redis;
    @Override
    public Result sign() {
    
    
        //1. 获取当前登录用户Id
        Long userId = UserHolder.getUser().getId();
        //2.获取日期:Redis中的offset偏移量是从0开始算,偏移量n代表的这个月的第n+1天
        int offset = LocalDateTime.now().getDayOfMonth() - 1;
        //3.拼接key存入Redis
        LocalDateTime now = LocalDateTime.now();
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = "sign:" + userId + keySuffix;
        //true代表的是存入BitMap中的二进制是1
        redis.opsForValue().setBit(key, offset, true);
        return Result.ok();
    }
}

`5. Service层实现连续签到功能`
```java
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
    
    private static final ObjectMapper mapper = new ObjectMapper();
    @Resource
    private StringRedisTemplate redis;
    @Override
    public Result signCount() {
    
    
        //1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        //2.获取日期
        LocalDateTime nowTime = LocalDateTime.now();
        //3.拼接key
        String keySuffix = nowTime.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = "sign:" + userId + keySuffix;
        //4.获取今天是第几天
        int day = nowTime.getDayOfMonth();
        //5.获取本月截至今天为止所有的签到记录,返回的是一个十进制数字
        List<Long> result = redis.opsForValue().bitField(
                key,
                BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(day)
                ).valueAt(0)
        );
        if (result.isEmpty() || result == null) {
    
    
            return Result.ok(0);
        }
        Long signCount = result.get(0);
        if (signCount == null || signCount == 0) {
    
    
            return Result.ok(0);
        }
        //6.遍历
        int count=0;
        while (true) {
    
    
            if ((signCount & 1) == 0) {
    
    
                //不为0,跳出循环
                break;
            } else {
    
    
                count++;
                signCount = signCount >> 1;
            }
        }
        //7.签到记录返回的十进制数字与1做 与运算

        return Result.ok(count);
    }
}

Guess you like

Origin blog.csdn.net/m0_50677223/article/details/132462167