java——redis随笔——实战——短信登录
前言:
此章节用到的知识点:mybatisPlus ;参考网址:https://www.bilibili.com/video/BV1Xu411A7tL?p=7&vd_source=79bbd5b76bfd74c2ef1501653cee29d6
正常新建一个接口:
再新建这个接口的实现类:
修改接口:
修改实现类:
然后就可以注入并使用了:
=============================================================
此章节还需要参考:创建一个类来统一结果返回:https://blog.csdn.net/qq_42331202/article/details/115823134
统一的返回结果是 json 格式, 类似下面的数据结构
{ "success": 布尔,//代表响应是否成功 "code": 数字,//响应码 "message": 字符串,//返回消息 "data":HashMap //返回数据 }
首先写一个接口, 定义数据返回响应码
package com.example.learn.common;/**
- @author : wangbo
- @version : 1.0
- @date :Create in 2021/4/18
- @description :
*/
public interface ResultCode {
// 成工时返回的状态码
public static Integer SUCCESS=20000;
// 失败是返回的状态码
public static Integer ERROR=20001;}
写统一返回结果的类
package com.example.learn.common;import lombok.Data;
import java.util.HashMap;
import java.util.Map;/**
@author : wangbo
@version : 1.0
@date :Create in 2021/4/18
@description :
*/
@Data
public class Result {
private boolean success;
private Integer code;
private String message;
private Map<String,Object> data=new HashMap<>();
private Result(){}
public static Result ok(){
Result result=new Result();
result.setSuccess(true);
result.setCode(ResultCode.SUCCESS);
result.setMessage("成功");
return result;
}
public static Result error(){
Result result=new Result();
result.setSuccess(false);
result.setCode(ResultCode.ERROR);
result.setMessage("失败");
return result;
}
public Result success(boolean success){
this.setSuccess(success);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}public Result message(String message) {
this.setMessage(message);
return this;
}public Result data(Map<String, Object> data) {
this.setData(data);
return this;
}}
其他参考:https://blog.csdn.net/qq_51447496/article/details/131414235?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-5-131414235-blog-104906051.235^v38^pc_relevant_anti_vip&spm=1001.2101.3001.4242.4&utm_relevant_index=8
@Data @NoArgsConstructor @AllArgsConstructor public class Result<T> {</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> code; </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String message; </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> T data; </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Result(T data) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.code = 200<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.message = "success"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.data =<span style="color: rgba(0, 0, 0, 1)"> data; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> Result(T data, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> success, String message) { </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (success) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.code = 200<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.message = "success"<span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.code = 500<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.message =<span style="color: rgba(0, 0, 0, 1)"> message; } </span><span style="color: rgba(0, 0, 255, 1)">this</span>.data =<span style="color: rgba(0, 0, 0, 1)"> data; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> Result(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> code, String message) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.code =<span style="color: rgba(0, 0, 0, 1)"> code; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.message =<span style="color: rgba(0, 0, 0, 1)"> message; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.data = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <T> Result<T><span style="color: rgba(0, 0, 0, 1)"> success(T data) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Result<><span style="color: rgba(0, 0, 0, 1)">(data); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <T> Result<T><span style="color: rgba(0, 0, 0, 1)"> fail(String message) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Result<>(500<span style="color: rgba(0, 0, 0, 1)">, message); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <T> Result<T> fail(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> code, String message) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Result<><span style="color: rgba(0, 0, 0, 1)">(code, message); }
}
======================================================================
拦截器:https://www.cnblogs.com/xiaobaibailongma/p/17060812.html
======================================================================
hutool 工具包快速入门:https://blog.csdn.net/Peaceuai/article/details/127953211
package com.hmdp.utils;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;public class RefreshTokenInterceptor implements HandlerInterceptor {
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisTemplate stringRedisTemplate; </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.stringRedisTemplate =<span style="color: rgba(0, 0, 0, 1)"> stringRedisTemplate; } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1.获取请求头中的token</span> String token = request.getHeader("authorization"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StrUtil.isBlank(token)) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2.基于TOKEN获取redis中的用户</span> String key = LOGIN_USER_KEY +<span style="color: rgba(0, 0, 0, 1)"> token; Map</span><Object, Object> userMap =<span style="color: rgba(0, 0, 0, 1)"> stringRedisTemplate.opsForHash().entries(key); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3.判断用户是否存在</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (userMap.isEmpty()) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 5.将查询到的hash数据转为UserDTO</span> UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, <span style="color: rgba(0, 0, 255, 1)">new</span> UserDTO(), <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 6.存在,保存用户信息到 ThreadLocal</span>
UserHolder.saveUser(userDTO);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7.刷新token有效期</span>
stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 8.放行</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 移除用户</span>
UserHolder.removeUser();
}
}
======================================================================
黑马视频地址:https://www.bilibili.com/video/BV1cr4y1671t?p=25&vd_source=79bbd5b76bfd74c2ef1501653cee29d6
参考笔记博客:https://blog.csdn.net/weixin_50523986/article/details/131815165
参考笔记博客:https://cyborg2077.github.io/2022/10/22/RedisPractice/#%E5%86%85%E5%AE%B9%E6%A6%82%E8%BF%B0
======================================================================
整体访问流程
IUserService
package com.hmdp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;import javax.servlet.http.HttpSession;
/**
<p>
服务类
</p>
@author 虎哥
@since 2021-12-22
*/
public interface IUserService extends IService<User> {Result sendCode(String phone, HttpSession session);
Result login(LoginFormDTO loginForm, HttpSession session);
Result sign();
Result signCount();
}
UserServiceImpl
package com.hmdp.service.impl;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.*;
import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;/**
<p>
服务实现类
</p>
@author 虎哥
@since 2021-12-22
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resource
private StringRedisTemplate stringRedisTemplate;@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 2. 如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
// 3. 符合,生成验证码
String code = RandomUtil.randomNumbers(6);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 4.保存验证码到 session</span> stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY +<span style="color: rgba(0, 0, 0, 1)"> phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 5.发送验证码</span> log.debug("发送短信验证码成功,验证码:{}"<span style="color: rgba(0, 0, 0, 1)">, code); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回ok</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Result.ok();
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1.校验手机号</span> String phone =<span style="color: rgba(0, 0, 0, 1)"> loginForm.getPhone(); </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (RegexUtils.isPhoneInvalid(phone)) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2.如果不符合,返回错误信息</span> <span style="color: rgba(0, 0, 255, 1)">return</span> Result.fail("手机号格式错误!"<span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3.从redis获取验证码并校验</span> String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY +<span style="color: rgba(0, 0, 0, 1)"> phone); String code </span>=<span style="color: rgba(0, 0, 0, 1)"> loginForm.getCode(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cacheCode == <span style="color: rgba(0, 0, 255, 1)">null</span> || !<span style="color: rgba(0, 0, 0, 1)">cacheCode.equals(code)) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不一致,报错</span> <span style="color: rgba(0, 0, 255, 1)">return</span> Result.fail("验证码错误"<span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 4.一致,根据手机号查询用户 select * from tb_user where phone = ?</span> User user = query().eq("phone"<span style="color: rgba(0, 0, 0, 1)">, phone).one(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 5.判断用户是否存在</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (user == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 6.不存在,创建新用户并保存</span> user =<span style="color: rgba(0, 0, 0, 1)"> createUserWithPhone(phone); } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7.保存用户信息到 redis中 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7.1.随机生成token,作为登录令牌</span> String token = UUID.randomUUID().toString(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7.2.将User对象转为HashMap存储</span> UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); Map</span><String, Object> userMap = BeanUtil.beanToMap(userDTO, <span style="color: rgba(0, 0, 255, 1)">new</span> HashMap<><span style="color: rgba(0, 0, 0, 1)">(), CopyOptions.create() .setIgnoreNullValue(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) .setFieldValueEditor((fieldName, fieldValue) </span>-><span style="color: rgba(0, 0, 0, 1)"> fieldValue.toString())); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7.3.存储</span> String tokenKey = LOGIN_USER_KEY +<span style="color: rgba(0, 0, 0, 1)"> token; stringRedisTemplate.opsForHash().putAll(tokenKey, userMap); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7.4.设置token有效期</span>
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 8.返回token</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Result.ok(token); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Result sign() { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1.获取当前登录用户</span> Long userId =<span style="color: rgba(0, 0, 0, 1)"> UserHolder.getUser().getId(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2.获取日期</span> LocalDateTime now =<span style="color: rgba(0, 0, 0, 1)"> LocalDateTime.now(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3.拼接key</span> String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"<span style="color: rgba(0, 0, 0, 1)">)); String key </span>= USER_SIGN_KEY + userId +<span style="color: rgba(0, 0, 0, 1)"> keySuffix; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 4.获取今天是本月的第几天</span> <span style="color: rgba(0, 0, 255, 1)">int</span> dayOfMonth =<span style="color: rgba(0, 0, 0, 1)"> now.getDayOfMonth(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 5.写入Redis SETBIT key offset 1</span> stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Result.ok(); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Result signCount() { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1.获取当前登录用户</span> Long userId =<span style="color: rgba(0, 0, 0, 1)"> UserHolder.getUser().getId(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2.获取日期</span> LocalDateTime now =<span style="color: rgba(0, 0, 0, 1)"> LocalDateTime.now(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3.拼接key</span> String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"<span style="color: rgba(0, 0, 0, 1)">)); String key </span>= USER_SIGN_KEY + userId +<span style="color: rgba(0, 0, 0, 1)"> keySuffix; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 4.获取今天是本月的第几天</span> <span style="color: rgba(0, 0, 255, 1)">int</span> dayOfMonth =<span style="color: rgba(0, 0, 0, 1)"> now.getDayOfMonth(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0</span> List<Long> result =<span style="color: rgba(0, 0, 0, 1)"> stringRedisTemplate.opsForValue().bitField( key, BitFieldSubCommands.create() .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(</span>0<span style="color: rgba(0, 0, 0, 1)">) ); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result == <span style="color: rgba(0, 0, 255, 1)">null</span> ||<span style="color: rgba(0, 0, 0, 1)"> result.isEmpty()) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 没有任何签到结果</span> <span style="color: rgba(0, 0, 255, 1)">return</span> Result.ok(0<span style="color: rgba(0, 0, 0, 1)">); } Long num </span>= result.get(0<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (num == <span style="color: rgba(0, 0, 255, 1)">null</span> || num == 0<span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> Result.ok(0<span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 6.循环遍历</span> <span style="color: rgba(0, 0, 255, 1)">int</span> count = 0<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 6.1.让这个数字与1做与运算,得到数字的最后一个bit位 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断这个bit位是否为0</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((num & 1) == 0<span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果为0,说明未签到,结束</span> <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">; }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果不为0,说明已签到,计数器+1</span> count++<span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 把数字右移一位,抛弃最后一个bit位,继续下一个bit位</span> num >>>= 1<span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Result.ok(count); } </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> User createUserWithPhone(String phone) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1.创建用户</span> User user = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> User(); user.setPhone(phone); user.setNickName(USER_NICK_NAME_PREFIX </span>+ RandomUtil.randomString(10<span style="color: rgba(0, 0, 0, 1)">)); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2.保存用户</span>
save(user);
return user;
}
}
UserController
package com.hmdp.controller;import cn.hutool.core.bean.BeanUtil;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.entity.UserInfo;
import com.hmdp.service.IUserInfoService;
import com.hmdp.service.IUserService;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;/**
<p>
前端控制器
</p>
@author 虎哥
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Resource
private IUserService userService;@Resource
private IUserInfoService userInfoService;/**
- 发送手机验证码
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone")String phone, HttpSession session) {
// 发送短信验证码并保存验证码
return userService.sendCode(phone, session);
}/**
- 登录功能
- @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// 实现登录功能
return userService.login(loginForm, session);
}/**
- 登出功能
- @return 无
*/
@PostMapping("/logout")
public Result logout(){
// TODO 实现登出功能
return Result.fail("功能未完成");
}@GetMapping("/me")
public Result me(){
// 获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}@GetMapping("/info/{id}")
public Result info(@PathVariable("id")Long userId){
// 查询详情
UserInfo info = userInfoService.getById(userId);
if (info == null) {
// 没有详情,应该是第一次查看详情
return Result.ok();
}
info.setCreateTime(null);
info.setUpdateTime(null);
// 返回
return Result.ok(info);
}@GetMapping("/{id}")
public Result queryUserById(@PathVariable("id")Long userId){
// 查询详情
User user = userService.getById(userId);
if (user == null) {
return Result.ok();
}
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 返回
return Result.ok(userDTO);
}@PostMapping("/sign")
public Result sign(){
return userService.sign();
}@GetMapping("/sign/count")
public Result signCount(){
return userService.signCount();
}
}
Result
package com.hmdp.dto;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;/***
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Boolean success;
private String errorMsg;
private Object data;
private Long total;</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> Result ok() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Result(<span style="color: rgba(0, 0, 255, 1)">true</span>, <span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> Result ok(Object data) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Result(<span style="color: rgba(0, 0, 255, 1)">true</span>, <span style="color: rgba(0, 0, 255, 1)">null</span>, data, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> Result ok(List<?><span style="color: rgba(0, 0, 0, 1)"> data, Long total) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Result(<span style="color: rgba(0, 0, 255, 1)">true</span>, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, data, total); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> Result fail(String errorMsg) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Result(<span style="color: rgba(0, 0, 255, 1)">false</span>, errorMsg, <span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); }
}
userDTO
package com.hmdp.dto;import lombok.Data;
@Data
public class UserDTO {
private Long id;
private String nickName;
private String icon;
}
LoginFormDTO
package com.hmdp.dto;import lombok.Data;
@Data
public class LoginFormDTO {
private String phone;
private String code;
private String password;
}
User
package com.hmdp.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.time.LocalDateTime;/**
<p>
</p>
@author 虎哥
@since 2021-12-22
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_user")
public class User implements Serializable {private static final long serialVersionUID = 1L;
/**
- 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;/**
- 手机号码
*/
private String phone;/**
- 密码,加密存储
*/
private String password;/**
- 昵称,默认是随机字符
*/
private String nickName;/**
- 用户头像
*/
private String icon = "";/**
- 创建时间
*/
private LocalDateTime createTime;/**
- 更新时间
*/
private LocalDateTime updateTime;}
RedisConstants
package com.hmdp.utils;public class RedisConstants {
public static final String LOGIN_CODE_KEY = "login:code:";
public static final Long LOGIN_CODE_TTL = 2L;
public static final String LOGIN_USER_KEY = "login:token:";
public static final Long LOGIN_USER_TTL = 36000L;</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Long CACHE_NULL_TTL = 2L<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Long CACHE_SHOP_TTL = 30L<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String CACHE_SHOP_KEY = "cache:shop:"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String LOCK_SHOP_KEY = "lock:shop:"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Long LOCK_SHOP_TTL = 10L<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String SECKILL_STOCK_KEY = "seckill:stock:"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String BLOG_LIKED_KEY = "blog:liked:"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String FEED_KEY = "feed:"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String SHOP_GEO_KEY = "shop:geo:"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String USER_SIGN_KEY = "sign:"<span style="color: rgba(0, 0, 0, 1)">;
}
RegexUtils
package com.hmdp.utils;import cn.hutool.core.util.StrUtil;
/**
@author 虎哥
*/
public class RegexUtils {
/**
- 是否是无效手机格式
- @param phone 要校验的手机号
- @return true: 符合,false:不符合
*/
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexPatterns.PHONE_REGEX);
}
/**- 是否是无效邮箱格式
- @param email 要校验的邮箱
- @return true: 符合,false:不符合
*/
public static boolean isEmailInvalid(String email){
return mismatch(email, RegexPatterns.EMAIL_REGEX);
}/**
- 是否是无效验证码格式
- @param code 要校验的验证码
- @return true: 符合,false:不符合
*/
public static boolean isCodeInvalid(String code){
return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
}// 校验是否不符合正则格式
private static boolean mismatch(String str, String regex){
if (StrUtil.isBlank(str)) {
return true;
}
return !str.matches(regex);
}
}
UserHolder
package com.hmdp.utils;import com.hmdp.dto.UserDTO;
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> saveUser(UserDTO user){ tl.set(user); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> UserDTO getUser(){ </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> tl.get(); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> removeUser(){ tl.remove(); }
}
- 对于 token 有效期的设置:每当有一个请求进来,有效期就应该刷新,需要改变拦截器的代码
- com/hmdp/interceptor/LoginInterceptor.java
// 这里是拦截器 不在 spring 容器中 不能同 @Autowired 注入 stringRedisTemplate // 换个方法 在配置类中注入 // 或者加个注解:@Configuration 就可以自动注入啦 private StringRedisTemplate stringRedisTemplate;</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 构造函数</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> LoginInterceptor(StringRedisTemplate stringRedisTemplate) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.stringRedisTemplate =<span style="color: rgba(0, 0, 0, 1)"> stringRedisTemplate; } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1 获取请求头中的token </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这一步需要前端把token保存到请求头中</span> String token = request.getHeader("authorization"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(StrUtil.isBlank(token)){ </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不存在 拦截 返回401状态码</span> response.setStatus(401<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2 基于token获取redis中的用户 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 只用 get 只能取出一个 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用entrise 取出的是一个map</span> Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY+<span style="color: rgba(0, 0, 0, 1)">token); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3 判断用户是否存在 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果userMap是null的话 自动返回一个空值</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (userMap.isEmpty()){ </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 5 将查询到的Map对象转为UserDto </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 忽略转换过程的错误:肯定不忽略</span> UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, <span style="color: rgba(0, 0, 255, 1)">new</span> UserDTO(),<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 6 存在 保存用户信息到Threadlocal中</span>
UserHolder.saveUser(userDTO);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7 刷新token有效期</span> stringRedisTemplate.expire(LOGIN_USER_KEY+<span style="color: rgba(0, 0, 0, 1)">token,LOGIN_USER_TTL, TimeUnit.MINUTES); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 8 放行</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; }</span></pre>
- com/hmdp/config/MvcConfig.java
package com.hmdp.config; import com.hmdp.interceptor.LoginInterceptor; import com.hmdp.interceptor.RefreshTokenInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MvcConfig implements WebMvcConfigurer {</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> @Configuration:这里可以注入</span>
@Autowired
private StringRedisTemplate stringRedisTemplate;@Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> addInterceptors(InterceptorRegistry registry) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 后执行 默认情况下按照添加顺序执行 拦截需要登录的请求</span> registry.addInterceptor(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> LoginInterceptor()) .excludePathPatterns( </span>"/user/code"<span style="color: rgba(0, 0, 0, 1)">, </span>"/user/login"<span style="color: rgba(0, 0, 0, 1)">, </span>"/blog/hot"<span style="color: rgba(0, 0, 0, 1)">, </span>"/shop/**"<span style="color: rgba(0, 0, 0, 1)">, </span>"/upload/**"<span style="color: rgba(0, 0, 0, 1)">, </span>"/shop-type/**"<span style="color: rgba(0, 0, 0, 1)">, </span>"/voucher/**"<span style="color: rgba(0, 0, 0, 1)"> ) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 确定执行顺序 越小越先执行</span> .order(1<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 先执行 拦截所有请求</span> registry.addInterceptor(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RefreshTokenInterceptor(stringRedisTemplate)) .addPathPatterns(</span>"/**"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 确定执行顺序 越小越先执行</span> .order(0<span style="color: rgba(0, 0, 0, 1)">); }
}