之前做的一个点选验证码,把代码贴出来,如果有需要可以根据实际情况修改
普通验证码:https://blog.csdn.net/weisong530624687/article/details/78861721
JAVA Graphics实现变色、渐变、阴影、倾斜、立体:
https://blog.csdn.net/weisong530624687/article/details/80048478
import java.awt.Color; import java.awt.image.BufferedImage; import java.util.Random; public abstract class AbstractRandCode { /** * 验证码缓存 */ public static final String LOGIN_CAPTCHA = "_LOGIN_CAPTCHA"; public static final String LOGIN_CAPTCHA_NAMESPACE = "RandCode"; /** * 随机码Img */ private BufferedImage bufferedImage; /** * 国际化语言 */ private String language; public BufferedImage getBufferedImage() { return bufferedImage; } public void setBufferedImage(BufferedImage bufferedImage) { this.bufferedImage = bufferedImage; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } /** * 获取给定范围随机色 * @param basicValue * @param extentValue * @return */ protected Color getRandColor(int basicValue, int extentValue) { Random random = new Random(); int basic = basicValue > 255 ? 255 : basicValue; int extent = extentValue > 255 ? 255 : extentValue; int r = basic + random.nextInt(extent - basic); int g = basic + random.nextInt(extent - basic); int b = basic + random.nextInt(extent - basic); return new Color(r, g, b); } }
import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.geom.Line2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import javax.imageio.ImageIO; import com.alibaba.fastjson.JSONArray; /** * 点选验证码 */ public class PointSelectRandCode extends AbstractRandCode implements RandCode { Log log = LogFactory.getLog(PointSelectRandCode.class); /** * 生成验证码的宽度 */ private static final int WIDTH = 230; /** * 生成验证码的高度 */ private static final int HEIGHT = 100; /** * 底部trip信息高度 */ private static final int BOTTOM_HEIGHT = 35; /** * 字符大小 */ private static final int FONT_SIZE = 24; /** * 定义点选文字图片验证码允许的误差值,单位是px */ private static final double ERROR_AMOUNT = FONT_SIZE / 3 * 2.0; /** * 干扰线个数 */ private static final int LINE_COUNT = 120; /** * 设置字符位置数 */ private static final Integer[] PLACE_ARRAY = new Integer[] {1, 2, 3, 4, 5}; /** * 需要校验的字符个数 */ private static final int VALIDATE_CHAR_NUM = 3; /** * 字符的颜色随机范围 */ @SuppressWarnings("unused") private static final Color[] CHAR_COLORS = {Color.BLACK, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.PINK, Color.ORANGE}; /** * 字符的颜色随机范围 */ //private static final Color[] LIGHT_CHAR_COLORS = {Color.CYAN, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.WHITE, Color.YELLOW}; private static final Color[] LIGHT_CHAR_COLORS = {new Color(72, 76, 119)}; private static String separator = File.separator; /** * 图片资源路径 */ private static final String IMG_PATH = separator + "static-res" + separator + "img" + separator + "login" + separator; /** * 背景图片资源路径 */ private static final String BACK_GROUD_IMG_PATH = IMG_PATH + "backgroundimg" + separator; /** * 是否设置随机线 */ private boolean isSetRandLine = false; /** * 校验队列 */ List<Point> validatedList; /** * 页面展示用户操作提示 */ String showOperatMessage; /** * 资源路径地址 */ String realPath; /** * 随机码队列 */ private List<String> randCodeList = new ArrayList<>(); @Override public boolean checkRandCode(String checkCode, String checkCodeKey) { CacheServiceByRedis redisCache = new CacheServiceByRedis(); String redisValidatedJson = redisCache.getVal(LOGIN_CAPTCHA_NAMESPACE, checkCodeKey); List<Point> redisValidatedList = JSONArray.parseArray(redisValidatedJson, Point.class); redisCache.remove(LOGIN_CAPTCHA_NAMESPACE, checkCodeKey); if (!checkRedisValidatedList(redisValidatedList)) { return false; } //校验checkCode String[] checkCodeArray = checkCode.split(","); if (!checkCheckCode(checkCodeArray)) { return false; } //比对checkCode和redisValidate 判断坐标参数是否正确 for (int i = 0; i < checkCodeArray.length; i++) { String[] checkCodePoint = checkCodeArray[i].split("_"); Point centerPoint = redisValidatedList.get(i); Point randPoint = new Point(); randPoint.setLocation(new Double(checkCodePoint[0]), new Double(checkCodePoint[1])); if (!isInnerCircle(randPoint, centerPoint, ERROR_AMOUNT)) { return false; } } return true; } /** * 获取圆心 * @param point * @return */ @SuppressWarnings("unused") private Point getCenterPoint(Point point) { Point centerPoint = new Point(); centerPoint.setLocation(point.getX() + FONT_SIZE / 2.0, point.getY() + FONT_SIZE / 2.0); return centerPoint; } /** * 判断是否在圆形内 * 判断点与圆心之间的距离和圆半径的关系 * @param randPoint * @param centerPoint * @return */ private boolean isInnerCircle(Point randPoint, Point centerPoint, double radius) { double distance = Math.hypot(randPoint.getX() - centerPoint.getX(), randPoint.getY() - centerPoint.getY()); return distance > radius ? false : true; } private boolean checkCheckCode(String[] checkCodeArray) { if (checkCodeArray.length != VALIDATE_CHAR_NUM) { return false; } for (int i = 0; i < checkCodeArray.length; i++) { if (EmptyUtils.isEmpty(checkCodeArray[i])) { return false; } } return true; } private boolean checkRedisValidatedList(List<Point> redisValidatedList) { return EmptyUtils.isNotEmpty(redisValidatedList) && redisValidatedList.size() == VALIDATE_CHAR_NUM; } @Override public AbstractRandCode getRandCode() { randCodeList = new ArrayList<>(); return getPointSelectRandCode(isEnLanguage(getLanguage())); } private AbstractRandCode getPointSelectRandCode(boolean isEnLanguage) { //生成背景图片 BufferedImage pointSelectImg = getBackGround(); Graphics graphics = pointSelectImg.getGraphics(); setGraphicsFont(isEnLanguage, graphics); //随机排序位置集合 @SuppressWarnings({"unchecked", "rawtypes"}) CopyOnWriteArrayList<Integer> randPlaceList = new CopyOnWriteArrayList(Arrays.asList(PLACE_ARRAY)); Collections.shuffle(randPlaceList); //list参数坐标参数 用于存储校验原点坐标 List<Point> validatedCodeList = new ArrayList<>(); //记录要校验的字符 StringBuilder readyClickCharBuilder = new StringBuilder(); //随机N个place不需要校验 List<Integer> notValidatedPlaces = getNotValidatePlaces(); Point pointLast = null; for (int i = 0; i < PLACE_ARRAY.length; i++) { // 5个汉字,只点4个 String randChar = getRandChar(true); randCodeList.add(randChar); int place = randPlaceList.get(i); Point point = getPoint(place); while (isCloser(point, pointLast)) { point = getPoint(place); } pointLast = point; Point pointNew = setRotateAndGradient(randChar, point.x, point.y, graphics); if (!notValidatedPlaces.contains(place)) { readyClickCharBuilder.append(randChar); validatedCodeList.add(pointNew); } } String operateMessage = getOperateMessage(isEnLanguage, readyClickCharBuilder); BufferedImage bufferBottomImg = getBottomImg(operateMessage, isEnLanguage); BufferedImage customImg = getCustomImg(); BufferedImage combinedImg = getCombinedImg(pointSelectImg, bufferBottomImg, customImg); PointSelectRandCode pointSelectRandCode = new PointSelectRandCode(); pointSelectRandCode.setBufferedImage(combinedImg); pointSelectRandCode.setShowOperatMessage(operateMessage); pointSelectRandCode.setValidatedList(validatedCodeList); return pointSelectRandCode; } private List<Integer> getNotValidatePlaces() { List<Integer> notValidatedPlaces = new ArrayList<>(); Random random = new Random(); while (true) { Integer notValidatedPlace = random.nextInt(PLACE_ARRAY.length) + 1; if (!notValidatedPlaces.contains(notValidatedPlace)) { notValidatedPlaces.add(notValidatedPlace); if (notValidatedPlaces.size() == (PLACE_ARRAY.length - VALIDATE_CHAR_NUM)) { break; } } } return notValidatedPlaces; } private void setGraphicsFont(boolean isEnLanguage, Graphics graphics) { if (isEnLanguage) { graphics.setFont(new Font("Times New Roman", Font.BOLD, FONT_SIZE)); } else { graphics.setFont(new Font("宋体", Font.BOLD, FONT_SIZE)); } } private boolean isCloser(Point point, Point pointLast) { if (EmptyUtils.isAnyoneEmpty(pointLast)) { return false; } if (Math.abs(point.getX() - pointLast.getX()) > FONT_SIZE || Math.abs(point.getY() - pointLast.getY()) > FONT_SIZE) { return false; } return true; } /** * 获取合并后的最终Img * @param pointSelectImg * @param bufferBottomImg * @param customImg * @return */ private BufferedImage getCombinedImg(BufferedImage pointSelectImg, BufferedImage bufferBottomImg, BufferedImage customImg) { BufferedImage combinedImg = new BufferedImage(WIDTH, HEIGHT + bufferBottomImg.getHeight() + customImg.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics combinedGrap = combinedImg.getGraphics(); //合并 combinedGrap.drawImage(pointSelectImg, 0, 0, null); combinedGrap.drawImage(customImg, 0, pointSelectImg.getHeight(), null); combinedGrap.drawImage(bufferBottomImg, 0, pointSelectImg.getHeight() + customImg.getHeight(), null); return combinedImg; } /** * 获取自定义img * @return */ private BufferedImage getCustomImg() { BufferedImage customImg = new BufferedImage(WIDTH, 15, BufferedImage.TYPE_INT_RGB); Graphics gra = customImg.getGraphics(); gra.setColor(new Color(60, 59, 65)); gra.fillRect(0, 0, customImg.getWidth(), customImg.getHeight()); return customImg; } /** * 旋转并且画出指定字符串 * @param drawString 需要旋转的字符串 * @param x 字符串的x坐标 * @param y 字符串的Y坐标 * @param graphics 画笔g * @return */ private Point setRotateAndGradient(String drawString, int x, int y, Graphics graphics) { Point point = new Point(); //如果在画字之前旋转图片 degree 旋转的角度 Random random = new Random(); int maxDegree = random.nextInt(2) % 2 == 0 ? 0 : 305; int degree = random.nextInt(45) + maxDegree; Graphics2D graphics2d = (Graphics2D) graphics.create(); //平移原点到图形环境的中心 ,这个方法的作用实际上就是将字符串移动到某一个位置 graphics2d.translate(x, y); //旋转文本 double degreeValue = degree * Math.PI / 180; graphics2d.rotate(degreeValue); double newPointX = Math.cos(degreeValue) * (FONT_SIZE / 2) + x; double newPointY = Math.sin(degreeValue) * (FONT_SIZE / 2) + y; point.setLocation(newPointX, newPointY); //创建循环渐变的GraphientPaint对象 GradientPaint paint = new GradientPaint(x, y, LIGHT_CHAR_COLORS[random.nextInt(LIGHT_CHAR_COLORS.length)], x + 24, y + 24, LIGHT_CHAR_COLORS[random.nextInt(LIGHT_CHAR_COLORS.length)], true); graphics2d.setPaint(paint); //特别需要注意的是,这里的画笔已经具有了上次指定的一个位置,所以这里指定的其实是一个相对位置 graphics2d.drawString(drawString, 0, 0); return point; } private String getOperateMessage(boolean isEnLanguage, StringBuilder readyClickCharBuilder) { StringBuilder operatMessage = new StringBuilder(); if (isEnLanguage) { operatMessage.append(" Click In Order"); } else { operatMessage.append(" 请依次点击"); } for (int i = 0; i < readyClickCharBuilder.length(); i++) { char charAt = readyClickCharBuilder.charAt(i); operatMessage.append(" \"" + charAt + "\""); } return operatMessage.toString(); } /** * 自定义每个位置的随机坐标 * @param place * @return */ private Point getPoint(int place) { int x = 0; int maxDynamicRange = WIDTH / 5 / 3; int minDynamicRange = WIDTH / 5 / 3; switch (place) { case 1: x = new Random().nextInt(maxDynamicRange) + minDynamicRange; break; case 2: x = new Random().nextInt(maxDynamicRange) + minDynamicRange + WIDTH / 5; break; case 3: x = new Random().nextInt(maxDynamicRange) + minDynamicRange + WIDTH / 5 * 2; break; case 4: x = new Random().nextInt(maxDynamicRange) + minDynamicRange + WIDTH / 5 * 3; break; case 5: x = new Random().nextInt(maxDynamicRange) + WIDTH / 5 * 4; break; default: break; } int y = new Random().nextInt(HEIGHT / 3) + HEIGHT / 3; if (place == 5) { y = new Random().nextInt(HEIGHT / 6 * 4) + HEIGHT / 6 * 2; } return new Point(x, y); } /** * 获取顶部要展示的Image * @param operateMessage * @return */ private BufferedImage getBottomImg(String operateMessage, boolean isEnLanguage) { // 创建底部图片 BufferedImage bufferImgTop = new BufferedImage(WIDTH, BOTTOM_HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics gra = bufferImgTop.getGraphics(); gra.fillArc(0, 0, WIDTH, BOTTOM_HEIGHT, 20, 50); String filePath = getRealPath() + IMG_PATH + "white.png"; File file = new File(filePath); BufferedImage sourceImg = null; try { sourceImg = ImageIO.read(new FileInputStream(file)); } catch (Exception e) { log.error(e); throw new BusinessException("获取验证码失败"); } //2.内部圆角处理 //填充指定的矩形 gra.setColor(new Color(60, 59, 65)); gra.fillRect(0, 0, WIDTH, BOTTOM_HEIGHT); gra.drawImage(sourceImg, 0, 0, WIDTH, BOTTOM_HEIGHT, null); //1.内部不做圆角处理 //gra.setColor(new Color(247, 249, 250)); //gra.fillRect(0, 0, bufferImgTop.getWidth(), bufferImgTop.getHeight()); // 设置文字背景颜色 Font font = new Font("宋体", 0, 14); gra.setFont(font); gra.setColor(Color.DARK_GRAY); if (isEnLanguage) { gra.drawString(operateMessage, 20, bufferImgTop.getHeight() / 2 + font.getSize() / 2); } else { gra.drawString(operateMessage, 32, bufferImgTop.getHeight() / 2 + font.getSize() / 2); } return bufferImgTop; } /** * 生成背景图片 * @return */ private BufferedImage getBackGround() { BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); //创建Graphics2D对象 Graphics2D graphics = (Graphics2D) image.getGraphics(); graphics.fillArc(0, 0, WIDTH, HEIGHT, 20, 50); Random random = new Random(); String filePath = getRealPath() + BACK_GROUD_IMG_PATH; File file = new File(filePath); File[] picFiles = file.listFiles(new FileFilter() { @Override public boolean accept(File file) { if (file.isDirectory()) { return false; } BufferedImage bi; try { bi = ImageIO.read(file); } catch (IOException e) { log.info(e); return false; } if (bi == null) { return false; } return true; } }); File picture = picFiles[random.nextInt(picFiles.length)]; BufferedImage sourceImg = null; try { sourceImg = ImageIO.read(new FileInputStream(picture)); } catch (Exception e) { log.error(e); throw new BusinessException("获取验证码失败"); } //2.内部圆角处理 //填充指定的矩形 graphics.setColor(new Color(60, 59, 65)); graphics.fillRect(0, 0, WIDTH, HEIGHT); graphics.drawImage(sourceImg, 0, 0, WIDTH, HEIGHT, null); BasicStroke basicStroke = new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); graphics.setStroke(basicStroke); setRandLine(graphics); //输出生成的验证码图片 graphics.dispose(); return image; } /** * 设置随机线 * @param graphics */ private void setRandLine(Graphics2D graphics) { if (!isSetRandLine) { return; } //绘制随机线数量/粗细/颜色/位置 for (int i = 0; i < LINE_COUNT; i++) { Line2D line = getRandLine(); graphics.draw(line); graphics.setColor(getRandColor(30, 150)); } } /** * 获取随机线 * @return */ private Line2D getRandLine() { Random random = new Random(); int x = random.nextInt(WIDTH - 1); int y = random.nextInt(HEIGHT - 1); int x1 = random.nextInt(100) + 1; int y1 = random.nextInt(120) + 1; return new Line2D.Double(x, y, x + x1, y + y1); } private String getRandChar(boolean isEnLanguage) { if (isEnLanguage) { return getRandomEnChar(); } return getRandomChineseChar(); } private String getRandomEnChar() { //混合字符串 String mixString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random random = new Random(); String randomEnChar = String.valueOf(mixString.charAt(random.nextInt(mixString.length()))); while (randCodeList.contains(randomEnChar)) { randomEnChar = getRandomEnChar(); } return randomEnChar; } /** * 获取随机中文字符 * @return */ private String getRandomChineseChar() { Random random = new Random(); Integer highPosition = 176 + Math.abs(random.nextInt(39)); Integer lowPosition = 161 + Math.abs(random.nextInt(93)); byte[] b = new byte[2]; b[0] = highPosition.byteValue(); b[1] = lowPosition.byteValue(); try { return new String(b, "GBk"); } catch (UnsupportedEncodingException e) { log.info(e); return getRandomChineseChar(); } } private boolean isEnLanguage(String language) { return !(EmptyUtils.isEmpty(language) || "zh_CN".equals(language)); } public List<Point> getValidatedList() { return validatedList; } public void setValidatedList(List<Point> validatedList) { this.validatedList = validatedList; } public String getShowOperatMessage() { return showOperatMessage; } public void setShowOperatMessage(String showOperatMessage) { this.showOperatMessage = showOperatMessage; } public String getRealPath() { return realPath; } public void setRealPath(String realPath) { this.realPath = realPath; } }
打赏
如果觉得我的文章对你有帮助,有钱就捧个钱场,没钱就捧个人场,欢迎点赞或转发 ,并请注明原出处,谢谢....