您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“springboot圖片驗(yàn)證碼功能模塊怎么實(shí)現(xiàn)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“springboot圖片驗(yàn)證碼功能模塊怎么實(shí)現(xiàn)”吧!
具體效果如下:
第一步:工具類
該工具類為生成驗(yàn)證碼圖片的核心,直接拷貝到項(xiàng)目即可,無需做修改;可個(gè)性化的參數(shù)全部對(duì)外提供的API,比如 字體大小
,背景顏色
,干擾線數(shù)量
,高寬
等都可以根據(jù)自己的需求設(shè)置對(duì)應(yīng)參數(shù);
代碼幾乎每一行都加了詳細(xì)的注釋;如果遇上特殊的個(gè)性化需求,調(diào)整一下這個(gè)工具類即可實(shí)現(xiàn)。
package com.feng.util; /** * @return null * @author Ladidol * @description * @date 2022/4/11 22:15 */ import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.util.Random; /** * 圖形驗(yàn)證碼生成 */ public class VerifyUtil { // 默認(rèn)驗(yàn)證碼字符集 private static final char[] chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; // 默認(rèn)字符數(shù)量 private final Integer SIZE; // 默認(rèn)干擾線數(shù)量 private final int LINES; // 默認(rèn)寬度 private final int WIDTH; // 默認(rèn)高度 private final int HEIGHT; // 默認(rèn)字體大小 private final int FONT_SIZE; // 默認(rèn)字體傾斜 private final boolean TILT; private final Color BACKGROUND_COLOR; /** * 初始化基礎(chǔ)參數(shù) * * @param builder */ private VerifyUtil(Builder builder) { SIZE = builder.size; LINES = builder.lines; WIDTH = builder.width; HEIGHT = builder.height; FONT_SIZE = builder.fontSize; TILT = builder.tilt; BACKGROUND_COLOR = builder.backgroundColor; } /** * 實(shí)例化構(gòu)造器對(duì)象 * * @return */ public static Builder newBuilder() { return new Builder(); } /** * @return 生成隨機(jī)驗(yàn)證碼及圖片 * Object[0]:驗(yàn)證碼字符串; * Object[1]:驗(yàn)證碼圖片。 */ public Object[] createImage() { StringBuffer sb = new StringBuffer(); // 創(chuàng)建空白圖片 BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); // 獲取圖片畫筆 Graphics2D graphic = image.createGraphics(); // 設(shè)置抗鋸齒 graphic.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 設(shè)置畫筆顏色 graphic.setColor(BACKGROUND_COLOR); // 繪制矩形背景 graphic.fillRect(0, 0, WIDTH, HEIGHT); // 畫隨機(jī)字符 Random ran = new Random(); //graphic.setBackground(Color.WHITE); // 計(jì)算每個(gè)字符占的寬度,這里預(yù)留一個(gè)字符的位置用于左右邊距 int codeWidth = WIDTH / (SIZE + 1); // 字符所處的y軸的坐標(biāo) int y = HEIGHT * 3 / 4; for (int i = 0; i < SIZE; i++) { // 設(shè)置隨機(jī)顏色 graphic.setColor(getRandomColor()); // 初始化字體 Font font = new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE); if (TILT) { // 隨機(jī)一個(gè)傾斜的角度 -45到45度之間 int theta = ran.nextInt(45); // 隨機(jī)一個(gè)傾斜方向 左或者右 theta = (ran.nextBoolean() == true) ? theta : -theta; AffineTransform affineTransform = new AffineTransform(); affineTransform.rotate(Math.toRadians(theta), 0, 0); font = font.deriveFont(affineTransform); } // 設(shè)置字體大小 graphic.setFont(font); // 計(jì)算當(dāng)前字符繪制的X軸坐標(biāo) int x = (i * codeWidth) + (codeWidth / 2); // 取隨機(jī)字符索引 int n = ran.nextInt(chars.length); // 得到字符文本 String code = String.valueOf(chars[n]); // 畫字符 graphic.drawString(code, x, y); // 記錄字符 sb.append(code); } // 畫干擾線 for (int i = 0; i < LINES; i++) { // 設(shè)置隨機(jī)顏色 graphic.setColor(getRandomColor()); // 隨機(jī)畫線 graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT), ran.nextInt(WIDTH), ran.nextInt(HEIGHT)); } // 返回驗(yàn)證碼和圖片 return new Object[]{sb.toString(), image}; } /** * 隨機(jī)取色 */ private Color getRandomColor() { Random ran = new Random(); Color color = new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256)); return color; } /** * 構(gòu)造器對(duì)象 */ public static class Builder { // 默認(rèn)字符數(shù)量 private int size = 4; // 默認(rèn)干擾線數(shù)量 private int lines = 10; // 默認(rèn)寬度 private int width = 80; // 默認(rèn)高度 private int height = 35; // 默認(rèn)字體大小 private int fontSize = 25; // 默認(rèn)字體傾斜 private boolean tilt = true; //背景顏色 private Color backgroundColor = Color.LIGHT_GRAY; public Builder setSize(int size) { this.size = size; return this; } public Builder setLines(int lines) { this.lines = lines; return this; } public Builder setWidth(int width) { this.width = width; return this; } public Builder setHeight(int height) { this.height = height; return this; } public Builder setFontSize(int fontSize) { this.fontSize = fontSize; return this; } public Builder setTilt(boolean tilt) { this.tilt = tilt; return this; } public Builder setBackgroundColor(Color backgroundColor) { this.backgroundColor = backgroundColor; return this; } public VerifyUtil build() { return new VerifyUtil(this); } } }
使用默認(rèn)參數(shù):
//生成圖片驗(yàn)證碼 Object[] verify = VerifyUtil.newBuilder().build().createImage();
自定義參數(shù)生成:
// 這個(gè)根據(jù)自己的需要設(shè)置對(duì)應(yīng)的參數(shù)來實(shí)現(xiàn)個(gè)性化 // 返回的數(shù)組第一個(gè)參數(shù)是生成的驗(yàn)證碼,第二個(gè)參數(shù)是生成的圖片 Object[] objs = VerifyUtil.newBuilder() .setWidth(120) //設(shè)置圖片的寬度 .setHeight(35) //設(shè)置圖片的高度 .setSize(6) //設(shè)置字符的個(gè)數(shù) .setLines(10) //設(shè)置干擾線的條數(shù) .setFontSize(25) //設(shè)置字體的大小 .setTilt(true) //設(shè)置是否需要傾斜 .setBackgroundColor(Color.WHITE) //設(shè)置驗(yàn)證碼的背景顏色 .build() //構(gòu)建VerifyUtil項(xiàng)目 .createImage(); //生成圖片
需要引入的maven依賴:
<!--redis相關(guān)配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- redis 連接池 --> <!--新版本連接池lettuce--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- 圖形驗(yàn)證碼 --> <dependency> <groupId>net.jodah</groupId> <artifactId>expiringmap</artifactId> <version>0.5.10</version> </dependency>
獲取相關(guān)的驗(yàn)證碼:
service層:
package com.feng.service; import org.cuit.epoch.result.Result; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @return null * @author Ladidol * @description * @date 2022/4/11 22:15 */ public interface VerifyService { /** * 創(chuàng)建圖片驗(yàn)證碼 * @param response * @param request * @throws IOException */ void createCode(HttpServletResponse response, HttpServletRequest request) throws IOException; /** * 檢查圖片驗(yàn)證碼 * @param * @param * @throws IOException */ Result<String> checkCode(String verificationCode); }
serviceimpl層:
package com.feng.service.impl; import com.feng.service.VerifyService; import com.feng.util.RedisServiceImpl; import com.google.common.net.HttpHeaders; import com.feng.util.VerifyUtil; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import java.time.Duration; /** * @return null * @author Ladidol * @description * @date 2022/4/11 22:15 */ @Service public class VerifyServiceImpl implements VerifyService { @Resource RedisServiceImpl redisUtil; /** * 生成圖片驗(yàn)證碼 * @param response * @param request * @throws IOException */ @Override public void createCode(HttpServletResponse response, HttpServletRequest request) throws IOException { //獲取session HttpSession session = request.getSession(); //獲得sessionId String id = session.getId(); System.out.println(); ResponseCookie cookie = ResponseCookie.from("JSESSIONID",id) .secure(true) .domain("") .path("/") .maxAge(Duration.ofHours(1)) .sameSite("None") .build(); //清除之前緩存的圖片驗(yàn)證碼 if (!String.valueOf(request.getSession().getAttribute("SESSION_VERIFY_CODE_"+id)).isEmpty()){ String getVerify = String.valueOf(request.getSession().getAttribute("SESSION_VERIFY_CODE_"+id)); redisUtil.del(getVerify); System.out.println("清除成功"); } //生成圖片驗(yàn)證碼,用的默認(rèn)參數(shù) Object[] verify = VerifyUtil.newBuilder().build().createImage(); //將驗(yàn)證碼存入session session.setAttribute("SESSION_VERIFY_CODE_" + id, verify[0]); //打印驗(yàn)證碼 System.out.println(verify[0]); //將驗(yàn)證碼存入redis redisUtil.set((String) verify[0],id,5*60); //將圖片傳給瀏覽器 BufferedImage image = (BufferedImage) verify[1]; response.setContentType("image/png"); response.setHeader(HttpHeaders.SET_COOKIE,cookie.toString()); OutputStream ops = response.getOutputStream(); ImageIO.write(image,"png",ops); } @Override public Result<String> checkCode(String verificationCode){ if (!redisUtil.hasKey(verificationCode)){ return new Result<>(false,"驗(yàn)證碼錯(cuò)誤"); } redisUtil.del(verificationCode); return R.success(); } }
這里面還會(huì)用到redis相關(guān)的工具類,我就不列出來了,想要的話可以看我以前的博客工具類戳這里
controller層:
這里有用到@RequiredArgsConstructor, 就是簡(jiǎn)單的注入而已, 如果想要詳細(xì)了解戳這里
package com.feng.controller; import lombok.RequiredArgsConstructor; import com.feng.annotation.LimitRequest; import com.feng.service.VerifyService; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @return null * @author Ladidol * @description 這里主要就是多種驗(yàn)證碼和登錄相關(guān)的東西 * @date 2022/4/11 21:46 */ @RestController @RequestMapping("/verify") @RequiredArgsConstructor//這是在lombok工具給的注入方式,真帥 public class VerifyController { private final VerifyService verifyService; /** * 獲取圖片驗(yàn)證碼 */ @LimitRequest(count = 5)//這個(gè)注解就是表示, 你在限制時(shí)間里(我們這里默認(rèn)是六秒鐘), 只能請(qǐng)求五次 @GetMapping("/getCode") public void getCode(HttpServletResponse response, HttpServletRequest request) throws IOException { verifyService.createCode(response, request); } @LimitRequest(count = 5)//這個(gè)注解就是表示, 你在限制時(shí)間里(我們這里默認(rèn)是六秒鐘), 只能請(qǐng)求五次 @GetMapping("/checkCode") public Result<String> checkCode(String code){ return verifyService.checkCode(code); } }
這里為了不被一直無限制的訪問該服務(wù), 我們用了一個(gè)限制ip訪問次數(shù)的注解@LimitRequest
annotion包下的注解類:
package com.feng.annotation; import java.lang.annotation.*; /** * @return null * @author Ladidol * @description 限制ip訪問次數(shù)注解 * @date 2022/4/11 22:15 */ @Documented @Target(ElementType.METHOD) // 說明該注解只能放在方法上面 @Retention(RetentionPolicy.RUNTIME) public @interface LimitRequest { long time() default 6000; // 限制時(shí)間 單位:毫秒 int count() default 3; // 允許請(qǐng)求的次數(shù) }
aspect包下的切面類:
package com.feng.aspect; import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpiringMap; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import com.feng.annotation.LimitRequest; import org.cuit.epoch.exception.AppException; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** * @return null * @author Ladidol * @description * @date 2022/4/11 22:15 */ @Aspect @Component public class LimitRequestAspect { private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> book = new ConcurrentHashMap<>(); // 定義切點(diǎn) // 讓所有有@LimitRequest注解的方法都執(zhí)行切面方法 @Pointcut("@annotation(limitRequest)") public void excudeService(LimitRequest limitRequest) { } @Around("excudeService(limitRequest)") public Object doAround(ProceedingJoinPoint pjp, LimitRequest limitRequest) throws Throwable { // 獲得request對(duì)象 RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); // 獲取Map對(duì)象, 如果沒有則返回默認(rèn)值 // 第一個(gè)參數(shù)是key, 第二個(gè)參數(shù)是默認(rèn)值 ExpiringMap<String, Integer> uc = book.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build()); Integer uCount = uc.getOrDefault(request.getRemoteAddr(), 0); if (uCount >= limitRequest.count()) { // 超過次數(shù),不執(zhí)行目標(biāo)方法 System.out.println("接口請(qǐng)求超過次數(shù)!"); throw new AppException("接口請(qǐng)求超過次數(shù)!"); } else if (uCount == 0) { // 第一次請(qǐng)求時(shí),設(shè)置有效時(shí)間 // uc.put(request.getRemoteAddr(), uCount + 1, ExpirationPolicy.CREATED, limitRequest.time(), TimeUnit.MILLISECONDS); } else { // 未超過次數(shù), 記錄加一 uc.put(request.getRemoteAddr(), uCount + 1); } book.put(request.getRequestURI(), uc); // result的值就是被攔截方法的返回值 Object result = pjp.proceed(); return result; } }
為了捕獲全局的異常拋出, 且符合restful規(guī)范我們加一個(gè)這個(gè)處理類:
handle包下面的全局異常類:
package org.cuit.epoch.handler; import lombok.extern.log4j.Log4j2; import org.cuit.epoch.exception.AppException; import org.cuit.epoch.result.R; import org.cuit.epoch.result.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice @Log4j2 public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public Result error(Exception e) { log.error(e.getMessage()); e.printStackTrace(); return R.fail(e.getMessage()); } @ExceptionHandler(AppException.class) @ResponseBody public Result error(AppException e) { log.error(e.getMessage()); e.printStackTrace(); return R.fail(e.getMessage()); } }
application.yaml文件:
spring: cache: type: redis redis: #redis連接配置 host: 自己redis的ip地址 port: redis端口 password: 密碼 jedis: pool: max-active: 8 max-wait: -1ms max-idle: 500 min-idle: 0 lettuce: shutdown-timeout: 0ms
最終項(xiàng)目結(jié)構(gòu)如下:
先得到一個(gè)驗(yàn)證碼:
驗(yàn)證一下是否成功:
成功結(jié)果:
驗(yàn)證失敗結(jié)果:
當(dāng)請(qǐng)求在規(guī)定時(shí)間內(nèi)的請(qǐng)求數(shù)超過規(guī)定的數(shù)量時(shí)或有報(bào)錯(cuò):
到此,相信大家對(duì)“springboot圖片驗(yàn)證碼功能模塊怎么實(shí)現(xiàn)”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。