1.注册
1.1DTO和VO
1.1.1首先第一步引入本节主题相关的依赖。
我构建的项目有很多哥模块,这是从各个模块抽取出来的,不能直接使用,目的是让读者知道需要哪些依赖。除此之外还有web,lombok等依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.6</version>
</dependency>
1.1.2 实体类、RegisterDTO、RegisterVO
POJO
package com.mc.domain.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String userName;
private String password;
private String trueName;
private String userPhone;
private Integer userStatus;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@TableField(exist = false)
private ArrayList<Integer> roles;
public User(String userName, String password, String trueName, String userPhone, Integer userStatus, ArrayList<Integer> roles){
this.userName = userName;
this.password = password;
this.trueName = trueName;
this.userPhone = userPhone;
this.userStatus = userStatus;
this.roles = roles;
}
}
DTO 前端表单提交封装进DTO
package com.mc.domain.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class UserRegisterDTO {
private String username;
private String password;
private String trueName;
private String userPhone;
private Integer roleId;
}
VO
package com.mc.domain.vo;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class RegisterVO {
private String userName;
private String trueName;
private String userPhone;
}
1.2 Controller层
package com.mc.Controller;
import com.mc.Service.UserService;
import com.mc.domain.dto.UserLoginDTO;
import com.mc.domain.dto.UserRegisterDTO;
import com.mc.domain.vo.LoginVO;
import com.mc.domain.vo.RegisterVO;
import com.mc.result.Result;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "用户管理")
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody UserLoginDTO userLoginDTO){
LoginVO loginVO = userService.login(userLoginDTO);
return Result.success(loginVO);
}
@PostMapping("/register")
public Result register(@RequestBody UserRegisterDTO userRegisterDTO){
RegisterVO registerVO = userService.register(userRegisterDTO);
return Result.success(registerVO);
}
}
1.3 ServiceImpl层
利用md5对密码进行加密存储进入数据库,拆解DTO,构建POJO存入数据库,再返回VO回前端,对更新时间和创建时间进行自动填充,利用mybatis-plus的@TableField(fill = FieldFill.****)完成。
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
// MD5加密方法
/**
* @param password
* @return
*/
private String md5Encrypt(String password) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(password.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5加密失败", e);
}
}
/**
* 注册
* @param registerDTO
* @return
*/
@Override
public RegisterVO register(UserRegisterDTO registerDTO) {
if(registerDTO.getPassword() == null || registerDTO.getPassword().isEmpty()){
throw new BusinessException(DATA_NOT_EXIST);
}
String encodedPassword = md5Encrypt(registerDTO.getPassword());
User user = User.builder()
.userName(registerDTO.getUsername())
.password(encodedPassword)
.trueName(registerDTO.getTrueName())
.userPhone(registerDTO.getUserPhone())
.userStatus(1)
.build();
userMapper.insert(user);
return RegisterVO.builder()
.userName(user.getUserName())
.trueName(user.getTrueName())
.userPhone(user.getUserPhone())
.build();
}
2.登陆
2.1 验证码逻辑

2.1.1先通过获取验证码发送生成验证码的请求到后端。
package com.mc.Controller;
import com.mc.Service.CodeService;
import com.mc.domain.dto.CodeDTO;
import com.mc.domain.vo.CodeVO;
import com.mc.result.Result;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "验证码接口")
@RestController
@RequestMapping("/code")
public class CodeController {
@Resource
private CodeService codeService;
/**
* 生成验证码
* @param codeDTO 验证码DTO
* @return 验证码
*/
@PostMapping("/generate")
public Result generate(@RequestBody CodeDTO codeDTO) {
// 生成验证码逻辑
CodeVO code = codeService.generate(codeDTO);
// 成功则返回前端成功消息
return Result.success(code);
}
}
2.1.2 同样封装了Codedto和Codevo
这样封装VO,是为了将redis的验证码和前端用户填入的验证码进行验证,所有code需要传会前端,并且返回生成验证码成功的提示。
package com.mc.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Bean;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CodeVO {
String code;
boolean success;
}
为什么这样封装dto,因为之后进入UserServiceImpl中需要通过电话为key去redis查询验证码。
package com.mc.domain.dto;
import lombok.Data;
@Data
public class CodeDTO {
private String phone;
}
将生成的验证码传入redis,并将"code:"+dto.getPhone()作为key。
package com.mc.Service.ServiceImpl;
import com.mc.Service.CodeService;
import com.mc.domain.dto.CodeDTO;
import com.mc.domain.vo.CodeVO;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Service
public class CodeServiceImpl implements CodeService{
@Resource(name = "stringRedisTemplate")
private StringRedisTemplate redisTemplate;
@Override
public CodeVO generate(CodeDTO codeDTO) {
String code = String.format("%06d", new Random().nextInt(10000000));
String key = "code:" + codeDTO.getPhone();
redisTemplate.opsForValue().set(key, code,1, TimeUnit.MINUTES);
return new CodeVO(code, true);
}
}
这样redis就可以获得生成的验证码,并且前端有一个生成验证码成功的提示


2.2 Jwt令牌
jwt工具类
package com.mc.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.Map;
/**
* JWT工具类
*/
@Component
public class JwtUtil {
// 与测试类中一致的密钥
private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 默认过期时间1小时(毫秒)
private static final long DEFAULT_EXPIRATION = 60 * 60 * 1000;
/**
* 生成JWT令牌
* @param claims 自定义数据
* @return 生成的JWT令牌
*/
public static String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.addClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + DEFAULT_EXPIRATION))
.compact();
}
/**
* 解析JWT令牌
* @param jwt JWT令牌字符串
* @return 解析后的Claims对象
*/
public static Claims parseToken(String jwt) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(jwt)
.getBody();
}
}
2.3 ServiceImpl层逻辑
逻辑:
1.先去数据库查是否有该用户,并得到用户实体
2.再去查密码是否正确,需先对DTO的密码进行md5加密再比较
3.去redis查验证码是否正确
4.jwt工具生成token
5.最后构建VO返回前端
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private CodeService codeService;
@Resource(name = "stringRedisTemplate")
private StringRedisTemplate redisTemplate;
@Resource
private JwtUtil jwtUtil;
/**
* 登录
* @param userLoginDTO
* @return
*/
@Override
public LoginVO login(UserLoginDTO userLoginDTO) {
//1.查询后端是否有该用户
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//仅仅只是一个包装sql的语句,不会执行查询
queryWrapper.eq(User::getUserName, userLoginDTO.getUserName());
User user = userMapper.selectOne(queryWrapper);
//2.验证密码
LoginVO loginVO = null;
// 用户不存在
if (user == null) {
throw new BusinessException(USER_NOT_EXIST);
}
// ========== 3. 校验密码 ==========
String inputEncodedPassword = md5Encrypt(userLoginDTO.getPassword());
if (!inputEncodedPassword.equals(user.getPassword())) {
throw new BusinessException(USERNAME_PASSWORD_ERROR);
}
String phone = userLoginDTO.getPhone();
String redisCode = redisTemplate.opsForValue().get("code:" + phone);
// 验证码不存在/已过期
if (redisCode == null) {
throw new RuntimeException("验证码已过期,请重新获取");
}
// 验证码不正确
if (!redisCode.equals(userLoginDTO.getCaptchaCode())) {
throw new RuntimeException("验证码错误");
}
Map<String, Object> claims = new HashMap<>();
claims.put("username", user.getUserName());
claims.put("id", user.getId());
String jwtToken = jwtUtil.generateToken(claims);
return LoginVO.builder()
.token(jwtToken)
.id(user.getId())
.userName(user.getUserName())
.trueName(user.getTrueName())
.userPhone(user.getUserPhone())
.roleId(user.getRoles() == null || user.getRoles().isEmpty() ? null : user.getRoles().get(0))
.build();
}
}

3.总结:
整体使用了redis技术存储验证码,使用jwt生成token,mybatis-plus做数据操作。

971

被折叠的 条评论
为什么被折叠?



