hmdp知识点
1. 前置知识
1.1 MyBatisPlus的基本使用
1.1.1 引入依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version>
</dependency>
1.1.2 建立实体类和数据库表
实体类:
@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;}
数据库表:
注解 | 所属框架 | 用途说明 |
---|---|---|
@Data | Lombok | 自动生成 getter/setter/toString/equals/hashCode |
@EqualsAndHashCode(callSuper = false) | Lombok | 控制 equals 和 hashCode 是否包含父类字段 |
@Accessors(chain = true) | Lombok | 支持链式调用(set 方法返回 this) |
@TableName("tb_user") | MyBatis Plus | 指定当前类对应的数据库表名 |
@TableId(value = "id", type = IdType.AUTO) | MyBatis Plus | 标明主键字段,并设置数据库主键列名和类型 |
1.1.3 application.yml
mybatis-plus:type-aliases-package: com.hmdp.entity # 别名扫描包
1.1.4 启动类扫描包
package com.hmdp;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("com.hmdp.mapper") // MyBatis扫描包
@SpringBootApplication
public class HmDianPingApplication {public static void main(String[] args) {SpringApplication.run(HmDianPingApplication.class, args);}
}
1.1.5 Mapper接口
import com.hmdp.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;public interface UserMapper extends BaseMapper<User> {}
BaseMapper 是什么?
BaseMapper<T>
是 MyBatis Plus 提供的一个接口,包含一系列常用的 CRUD 操作方法(增删改查),这些方法已经实现好了,你可以直接使用。
常用方法举例:
方法名 | 功能说明 |
---|---|
int insert(User entity) | 插入一条记录 |
int deleteById(Serializable id) | 根据主键删除一条记录 |
int updateById(User entity) | 根据主键更新记录 |
User selectById(Serializable id) | 根据主键查询记录 |
List<User> selectList(QueryWrapper<User> wrapper) | 查询满足条件的多条记录 |
List<User> selectAll() | 查询所有记录(需自己定义 SQL 或扩展 Wrapper) |
1.1.6 Service接口
package com.hmdp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.hmdp.entity.User;public interface IUserService extends IService<User> {}
IService 是什么?
IService<T>
是 MyBatis Plus 提供的一个 通用服务接口,封装了常见的 业务层操作方法(CRUD),例如:
方法名 | 功能说明 |
---|---|
boolean save(User entity) | 插入一条记录 |
boolean removeById(Serializable id) | 根据主键删除记录 |
boolean updateById(User entity) | 根据主键更新记录 |
User getById(Serializable id) | 根据主键查询记录 |
List<User> list() | 查询所有记录 |
IPage<User> page(IPage<User> page) | 分页查询数据 |
boolean saveOrUpdate(User entity) | 存在则更新,不存在则插入 |
1.1.8 ServiceImpl实现类
package com.hmdp.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {}
UserServiceImpl 是 IUserService 的实现类 | 继承 ServiceImpl ,获得通用 CRUD 方法 |
必须指定两个泛型参数 | Mapper 和 Entity |
使用 @Service 注解 | 让 Spring 管理这个类 |
可以直接使用通用方法 | 如 save() , getById() , list() 等 |
支持扩展自定义方法 | 在接口中声明,在实现类中实现 |
1.1.9 Controller接口
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Resourceprivate IUserService userService;@Resourceprivate IUserInfoService userInfoService;/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// TODO 发送短信验证码并保存验证码return Result.fail("功能未完成");}/*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// TODO 实现登录功能return Result.fail("功能未完成");}/*** 登出功能* @return 无*/@PostMapping("/logout")public Result logout(){// TODO 实现登出功能return Result.fail("功能未完成");}@GetMapping("/me")public Result me(){// TODO 获取当前登录的用户并返回return Result.fail("功能未完成");}@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);}
}
1.2 校验手机号/验证码格式的工具类
注意:要引入hutool依赖。
package com.hmdp.utils;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}$";/*** 邮箱正则*/public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";/*** 密码正则。4~32位的字母、数字、下划线*/public static final String PASSWORD_REGEX = "^\\w{4,32}$";/*** 验证码正则, 6位数字或字母*/public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";}
package com.hmdp.utils;import cn.hutool.core.util.StrUtil;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);}
}
2. 短信登陆
2.1 基于Session实现登陆
用户第一次访问服务器时,服务器内部会生成一个session,这个session属于这个用户,有一个sessionID,服务器会自动将sessionID传递给浏览器,浏览器会自动保存sessionID,之后浏览器的每一次请求都会自动携带这个sessionID
2.2.1 实现发送短信验证码功能
业务流程:
- 前端提交手机号到后端
- 后端校验手机号
- 手机号格式不符合,返回手机号格式错误
- 手机号格式符合,下一步
- 生成验证码
- 保存验证码到当前用户的session中
- 将验证码发送给前端
前端向后端发送请求的俩种方式(POST请求):
- http://192.168.31.20:8080/api/user/code?phone=17771818987(通过Nginx代理转发)
- http://localhost:8081/code?phone=17771818987(直接访问后端)
UserController.java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Resourceprivate IUserService userService;/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// 发送短信验证码并保存验证码return userService.sendCode(phone,session);}}
IUserService.java
public interface IUserService extends IService<User> {/*** 登陆发送短信验证码接口* @param phone* @param session* @return*/Result sendCode(String phone, HttpSession session);
}
UserServiceImpl.java
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {/*** 发送短信验证码实现* @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 1. 校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2. 不符合,返回错误信息return Result.fail("手机号格式错误");}// 3. 符合,设生成验证码String code = RandomUtil.randomNumbers(6);// 通过hutool包,生成6位随机数// 4. 保存验证码到sessionsession.setAttribute("code", code);// 5. 发送验证码给前端log.debug("发送短信验证码成功, 短信验证码是:{}", code);// 返回OKreturn Result.ok();}
}
2.2.2 实现短信验证码登陆和注册
前端参数:
- 请求方式:POST
- 请求路径:/user/login
- 请求参数:phone, code(json数据)
- 返回值:无
业务流程:
- 前端传递phone和code
- 通过session对象校验验证码
- 验证码错误,返回“验证码错误”
- 验证码正确,去数据库根据手机号查询用户
- 用户是否存在
- 用户不存在,创建新用户,保存新用户的数据库
- 用户存在,保存用户信息到session
LoginFormDTO对象:
@Data
public class LoginFormDTO {private String phone;private String code;private String password;
}
2.2.3 实现登陆校验拦截器
业务流程:
- 请求并携带cookie
- 从session获取用户
- 判断用户是否存在session中
- 用户存在,表示手机号和验证码已经核验通过
- 用户不存在,拦截
2.2 集群的session共享问题
2.3 基于Redis实现共享session登陆