

UserController控制层
控制层调用service层的接口
- /**
- * 发送手机验证码
- */
- @PostMapping("code")
- public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
- return userService.sendCode(phone,session);
- }
IUserService接口
接口定义sendCode()方法
- public interface IUserService extends IService<User> {
-
- Result sendCode(String phone, HttpSession session);
- }
UserServiceImpl实现接口方法
- @Slf4j
- @Service
- public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{
-
- @Override
- public Result sendCode(String phone, HttpSession session) {
- //1. 校验手机号
- if (RegexUtils.isPhoneInvalid(phone)) {
- //2.如果不符合,返回错误信息
- return Result.fail("手机号格式错误");
- }
-
- //3. 符合,生成验证码
- String code = RandomUtil.randomNumbers(6);
-
- //4. 保存验证码到session
- session.setAttribute("code",code);
-
- //5. 发送验证码
- log.debug("发送短信验证码成功,验证码:{}",code);
-
- //返回ok
- return Result.ok();
- }
- }
注意:
上述代码中使用到了许多工具类,各个工具类的定义如下:
RegexUtils (校验手机号)
- public class RegexUtils {
- /**
- * 是否是无效手机格式
- * @param phone 要校验的手机号
- * @return true:符合,false:不符合
- */
- public static boolean isPhoneInvalid(String phone){
- return mismatch(phone, RegexPatterns.PHONE_REGEX);
- }
- }
RegexUtils 中调用了 RegexPatterns(正则校验)
- public abstract class RegexPatterns {
- /**
- * 手机号正则
- */
- public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
- }
我们重启服务,在前端页面输入手机号,点击获取验证码,在idea中的日志:

说明该功能实现完成!

UserController控制层
- @PostMapping("/login")
- public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
- // 实现登录功能
- return userService.login(loginForm, session);
- }
IUserService接口
接口定义login()方法
- public interface IUserService extends IService<User> {
-
- Result login(LoginFormDTO loginForm, HttpSession session);
- }
UserServiceImpl实现接口方法
- @Override
- public Result login(LoginFormDTO loginForm, HttpSession session) {
-
- //1. 校验手机号
- String phone = loginForm.getPhone();
- if (RegexUtils.isPhoneInvalid(phone)) {
- return Result.fail("手机号格式错误");
- }
-
- //2. 校验验证码
- Object cacheCode = session.getAttribute("code");
- String code = loginForm.getCode();
- if (cacheCode == null || !cacheCode.toString().equals(code)){
- //3. 不一致,报错
- return Result.fail("验证码错误");
- }
-
- //4.一致,根据手机号查询用户
- User user = query().eq("phone", phone).one();
-
- //5. 判断用户是否存在
- if (user == null){
- //6. 不存在,创建新用户
- user = createUserWithPhone(phone);
- }
-
- //7.保存用户信息到session
- session.setAttribute("user",BeanUtil.copyProperties(user,UserDTO.class));
- return Result.ok();
- }
在login()方法中调用createUserWithPhone()方法创建用户
- private User createUserWithPhone(String phone) {
-
- // 1.创建用户
- User user = new User();
- user.setPhone(phone);
- user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
-
- // 2.保存用户
- save(user);
- return user;
- }

拦截器 LoginInterceptor
此处使用拦截器校验用户是否存在,存在就登入;反之return false
- public class LoginInterceptor implements HandlerInterceptor {
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- //1. 获取session
- HttpSession session = request.getSession();
- //2.获取session中的用户
- Object user = session.getAttribute("user");
- //3. 判断用户是否存在
- if (user == null){
- //4. 不存在,拦截
- response.setStatus(401);
- return false;
- }
-
- //5. 存在 保存用户信息到ThreadLocal
- UserHolder.saveUser((UserDTO) user);
- //6. 放行
- return true;
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- //移除用户
- UserHolder.removeUser();
- }
- }
配置拦截器 MvcConfig
使拦截器LoginInterceptor 生效!并定义拦截的对象。
在SpringBoot中可以使用addPathPatterns配置需要拦截的路径、excludePathPatterns配置不要拦截的路径。
此处,表示放行的资源(即无需登入,就可以访问的页面)
- @Configuration
- public class MvcConfig implements WebMvcConfigurer {
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // 登录拦截器
- registry.addInterceptor(new LoginInterceptor())
- .excludePathPatterns(
- "/shop/**",
- "/voucher/**",
- "/shop-type/**",
- "/upload/**",
- "/blog/hot",
- "/user/code",
- "/user/login"
- );
- }
- }
注意:
上述的一系列代码,我们还做了安全性考虑,我们使用了UserDTO,并没有直接使用User,一来减少了数据传输的成本,二来避免了User中的敏感数据泄露!
UserDTO
- @Data
- public class UserDTO {
- private Long id;
- private String nickName;
- private String icon;
- }
再来看看这一张图,当程序到达性能瓶颈的时候,我们需要将原先的一台Tomcat,扩展Tomcat集群,但是每一台Tomcat都会有一个自己的Session空间(不同Tomcat的Session是相互独立的)。如果在登入方面我们不做调整就会出现登入失败的问题。
早期Tomcat是有做过Session拷贝的!但是这种方案会导致内存浪费;在拷贝过程中会产生时延,如果在这个延迟之内有人进行访问那么一样会出现问题!
所以我们急需一种可以代替Session的东西,此物需要满足:
显然用Redis来实现短信登入功能有着天然的优势。
