一個不正經的目錄
- 生成驗證碼
- 新建驗證碼物體類
- 新建驗證碼工具類
- 修改攔截器配置類
- 修改兩個DTO
- SpringBoot集成Redis
- 更新yml組態檔
- 修改UserController
生成驗證碼
驗證碼是一個保障介面和用戶密碼安全的良好工具,在專案中進行一些比較“危險”的操作的時候我們需要讓輸入驗證碼才能進入到下一步,話不多說,先來撰寫一個驗證碼工具類:
新建驗證碼物體類
在bean子包下新建VerifyCode物體類:
/**
* @Author Alfalfa99
* @Date 2020/9/19 22:13
* @Version 1.0
* 驗證碼物體類
*/
@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;
/**
* @Author Alfalfa99
* @Date 2020/9/19 22:10
* @Version 1.0
* 驗證碼生成工具類
*/
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;
/**
* 設定背景顏色及大小,干擾線
*
* @param graphics
* @param width
* @param height
*/
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);
}
}
/**
* 生成隨機字符
*
* @param width 寬度
* @param height 高度
* @param os
* @return
* @throws IOException
*/
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;
}
/**
* 驗證碼生成
*
* @param width
* @param height
* @return
*/
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;
}
/**
* 設定字符顏色大小
*
* @param g
* @param randomStr
*/
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("/**")
.excludePathPatterns("/**/login/**", "/**/register/**", "/**/verifyCode/**")
.excludePathPatterns("/**/*.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包下新建該自定義例外類
/**
* @Author Alfalfa99
* @Date 2020/9/20 10:48
* @Version 1.0
*/
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()方法去獲得相應的驗證碼,如果驗證碼存在,那么執行接下來的業務代碼,如果驗證碼不存在,則拋出例外要求用戶重新填寫驗證碼,
我們這篇博客的內容到這里就告一段落了,在下一篇博客就是本系列的最后一篇博客,我們會講解分頁和排序相關的內容
本次博客的內容也到此為止了,如果對博客內容有疑問可以私信聯系筆者,如果這篇文章對你有用希望你能點一個贊,謝謝~
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/131896.html
標籤:其他
上一篇:近日所想
下一篇:Mysql用戶與權限操作
