一个不正经的目录
- 生成验证码
-
- 新建验证码实体类
- 新建验证码工具类
- 修改拦截器配置类
- 修改两个DTO
- SpringBoot集成Redis
-
- 更新yml配置文件
- 修改UserController
生成验证码
验证码是一个保障接口和用户密码安全的良好工具,在项目中进行一些比较“危险”的操作的时候我们需要让输入验证码才能进入到下一步,话不多说,先来编写一个验证码工具类:
新建验证码实体类
在bean子包下新建VerifyCode实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class VerifyCode {
private String code;
private byte[] imgBytes;
private long expireTime;
}
新建验证码工具类
在util包下新建一个VerifyCodeUtil:
import Echo.alfalfa.MemberManager.model.bean.VerifyCode;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
public class VerifyCodeUtil extends org.apache.commons.lang3.RandomUtils {
private static final char[] CODE_SEQ = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9'};
private static final char[] NUMBER_ARRAY = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
private static Random random = new Random();
public static String randomString(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(String.valueOf(CODE_SEQ[random.nextInt(CODE_SEQ.length)]));
}
return sb.toString();
}
public static String randomNumberString(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(String.valueOf(NUMBER_ARRAY[random.nextInt(NUMBER_ARRAY.length)]));
}
return sb.toString();
}
public static Color randomColor(int fc, int bc) {
int f = fc;
int b = bc;
Random random = new Random();
if (f > 255) {
f = 255;
}
if (b > 255) {
b = 255;
}
return new Color(f + random.nextInt(b - f), f + random.nextInt(b - f),
f + random.nextInt(b - f));
}
public static int nextInt(int bound) {
return random.nextInt(bound);
}
private static final String[] FONT_TYPES = { "\u5b8b\u4f53", "\u65b0\u5b8b\u4f53", "\u9ed1\u4f53",
"\u6977\u4f53", "\u96b6\u4e66" };
//验证码字符位数
private static final int VALICATE_CODE_LENGTH = 5;
private static void fillBackground(Graphics graphics, int width, int height) {
// 填充背景
graphics.setColor(Color.WHITE);
//设置矩形坐标x y 为0
graphics.fillRect(0, 0, width, height);
// 加入干扰线条
for (int i = 0; i < 8; i++) {
//设置随机颜色算法参数
graphics.setColor(randomColor(40, 150));
Random random = new Random();
int x = random.nextInt(width);
int y = random.nextInt(height);
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
graphics.drawLine(x, y, x1, y1);
}
}
public String generate(int width, int height, OutputStream os) throws IOException {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = image.getGraphics();
fillBackground(graphics, width, height);
String randomStr = randomString(VALICATE_CODE_LENGTH);
createCharacter(graphics, randomStr);
graphics.dispose();
//设置JPEG格式
ImageIO.write(image, "JPEG", os);
return randomStr;
}
public VerifyCode generate(int width, int height) {
VerifyCode verifyCode = null;
try (
//将流的初始化放到这里就不需要手动关闭流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
) {
String code = generate(width, height, baos);
verifyCode = new VerifyCode();
verifyCode.setCode(code);
verifyCode.setImgBytes(baos.toByteArray());
} catch (IOException e) {
verifyCode = null;
}
return verifyCode;
}
private void createCharacter(Graphics g, String randomStr) {
char[] charArray = randomStr.toCharArray();
for (int i = 0; i < charArray.length; i++) {
//设置RGB颜色算法参数
g.setColor(new Color(50 + nextInt(100),
50 + nextInt(100), 50 + nextInt(100)));
//设置字体大小,类型
g.setFont(new Font(FONT_TYPES[nextInt(FONT_TYPES.length)], Font.BOLD, 26));
//设置x y 坐标
g.drawString(String.valueOf(charArray[i]), 15 * i + 5, 19 + nextInt(8));
}
}
}
通过该类中的generate()
方法我们就可以获得一个图片验证码了,我们需要在UserController中添加这个方法,将这个图片验证码返回给前端:
@PostMapping("/verifyCode")
public void verifyCode(HttpServletResponse response) throws IOException {
VerifyCodeUtil verifyCodeUtil = new VerifyCodeUtil();
//设置长宽
VerifyCode verifyCode = verifyCodeUtil.generate(80, 28);
//设置响应头
response.setHeader("Pragma", "no-cache");
//设置响应头
response.setHeader("Cache-Control", "no-cache");
//在代理服务器端防止缓冲
response.setDateHeader("Expires", 0);
//设置响应内容类型
response.setContentType("image/jpeg");
response.getOutputStream().write(verifyCode.getImgBytes());
response.getOutputStream().flush();
}
修改拦截器配置类
我们在InterceptorConfig拦截器配置类中要放行通向获取验证码接口的道路噢,不然就会陷入“死锁”–我要验证码才能登陆,我要登录才能获取验证码。
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截所有目录,除了通向login和register的接口
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("loginregisterverifyCode*.html", "*.js", "*.css");
}
那么我们现在测试一下我们的验证码是否能成功的返回给前端
可以看到我们现在确实完成了验证码的生成,并且将其返回给了前端,但是问题来了,用户登录或注册时,如何携带这个验证码和验证其是否正确呢?我们先来解决第一个问题!
修改两个DTO
我们分别在UserLoginDTO和UserRegisterDTO中增加一个verifyCode字段即可完成:
//登录
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginDTO {
@ApiModelProperty(example = "登录名")
@NotNull(message = "账号不允许为空")
private String username;
@ApiModelProperty(example = "密码")
@NotNull(message = "密码不允许为空")
private String password;
@ApiModelProperty(example = "验证码")
@NotNull(message = "验证码不允许为空")
private String verifyCode;
}
//注册
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterDTO {
@ApiModelProperty(example = "登录名")
@NotNull(message = "用户名不允许为空")
private String username;
@ApiModelProperty(example = "密码")
@NotNull(message = "密码不允许为空")
private String password;
@ApiModelProperty(example = "昵称")
@NotNull(message = "昵称不允许为空")
private String nickname;
@ApiModelProperty(example = "验证码")
@NotNull(message = "验证码不允许为空")
private String verifyCode;
}
那么我们就要解决第二个问题了,如何服务端如何保存这个生成的验证码并验证其是否正确呢?
SpringBoot集成Redis
在本系列第一篇博客中,如果有细心的读者应该已经发现了,我们引入了SpringBoot集成Redis的相关依赖spring-boot-starter-data-redis
,那么接下来对我们的代码进行更新吧!(redis的安装和开启过于简单,不在此处赘述了,一分钟就能启动)
更新yml配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: xxx
password: xxx
url: xxx
#在下面追加如下内容
redis:
host: 127.0.0.1
port: 6379
在yml中追加如下内容,我们连接本地的redis,监听端口为默认的6379。
修改UserController
首先我们先通过构造方法注入操作redis
private final UserService userService;
private final RedisTemplate redisTemplate;
public UserController(UserService userService, RedisTemplate redisTemplate) {
this.userService = userService;
this.redisTemplate = redisTemplate;
}
RedisTemplate 是Spring框架为我们提供的用于操作redis的工具类,如果读者有使用过jedis,那么对其应该并不陌生。
然后我们前往生成验证码的方法中添加代码:
@PostMapping("/verifyCode")
public void verifyCode(HttpServletResponse response) throws IOException {
VerifyCodeUtil verifyCodeUtil = new VerifyCodeUtil();
//设置长宽
VerifyCode verifyCode = verifyCodeUtil.generate(80, 28);
//获取验证码中的字符
String code = verifyCode.getCode();
//将验证码中的字符写入redis、过期时间为300秒钟
redisTemplate.opsForValue().set("vc_" + code, "1", 300, TimeUnit.SECONDS);
//设置响应头
response.setHeader("Pragma", "no-cache");
//设置响应头
response.setHeader("Cache-Control", "no-cache");
//在代理服务器端防止缓冲
response.setDateHeader("Expires", 0);
//设置响应内容类型
response.setContentType("image/jpeg");
response.getOutputStream().write(verifyCode.getImgBytes());
response.getOutputStream().flush();
}
在这里我们存入redis的内容为{vc_code:1}(在此处不去处理两个验证码相同的情况,300秒过期时间还能相同那确实挺幸运的)
在登录和注册时,我们从相应的DTO中取出验证码,并与在Redis中进行查询,如果存在该验证码则进行下面的业务流程,如果不存在该验证码则抛出验证码错误异常(VerifyException)
请在exception包下新建该自定义异常类
public class VerifyException extends AuthenticationException {
public VerifyException(String msg) {
super(msg);
}
public VerifyException(String msg, Throwable t) {
super(msg, t);
}
}
之后我们去全局异常处理类中添加上这个异常
```
else if (e instanceof VerifyException){
//验证码错误
return new CommonResult<>(40006, "Error", e.getMessage());
}
```
完成上述步骤之后回到我们的UserController中,对我们的登录以及注册方法进行验证码验证
@PostMapping("/login")
public CommonResult<String> login(@RequestBody UserLoginDTO ulDTO, HttpServletRequest request) {
if (null == redisTemplate.opsForValue().get("vc_" + ulDTO.getVerifyCode())) {
throw new VerifyException("验证码错误");
}
redisTemplate.delete("vc_" + ulDTO.getVerifyCode());
String token = userService.userLogin(ulDTO);
return new CommonResult<>(20000, "OK", token);
}
@PostMapping("/register")
public CommonResult<String> register(@RequestBody UserRegisterDTO urDTO) {
if (null == redisTemplate.opsForValue().get("vc_" + urDTO.getVerifyCode())) {
throw new VerifyException("验证码错误");
}
userService.userRegister(urDTO);
return new CommonResult<>(20000, "OK", "注册成功");
}
我们通过redisTemplate.opsForValue().get()
方法去获得相应的验证码,如果验证码存在,那么执行接下来的业务代码,如果验证码不存在,则抛出异常要求用户重新填写验证码。
我们这篇博客的内容到这里就告一段落了,在下一篇博客就是本系列的最后一篇博客,我们会讲解分页和排序相关的内容
本次博客的内容也到此为止了,如果对博客内容有疑问可以私信联系笔者,如果这篇文章对你有用希望你能点一个赞,谢谢~