Java 发送验证码(多平台)和 图形验证码 的实现

1、前言:

在用户进行登录或者注册时,都会有输入验证码的相关操作,这就会涉及到发送验证码给用户的操作,发送方式大致分为两种:短信和邮箱,其中他们又有各种平台的发送方式,本次分别开发了阿里云的短信发送QQ的邮箱发送,其他的类似。

图形验证码则是防止恶意刷短信次数设置的,主要是如何将生成的字符串转成图形验证码的过程。

2、操作步骤:

(1)用户点击发送短信;
(2)后端编写发送短信的接口;
(3)如果请求次数过多,则需要一个创建图形验证码的接口;
(4)登录、注册。

3、代码:

(1) controller:

 /**
     * 发送验证码到对应手机号或者邮箱
     * @param loginName
     * @return
     */
    @PostMapping("/sendCode")
    public ResponseResult<Boolean> sendCode(@MyRequestBody String loginName, @MyRequestBody String imageCode) {
        // 判断是否需要检查图形验证码(同一登录名每天请求超过三次,则需要检查)
        RAtomicLong atomicLong = redissonClient.getAtomicLong("sendCode-" + loginName);
        if (atomicLong.isExists()) {
            long sendNumber = atomicLong.get();
            atomicLong.set(sendNumber+1);
        } else {
            // 设置初始请求次数和存在时长
            atomicLong.set(1L);
            atomicLong.expire(24, TimeUnit.HOURS);
        }

        if (atomicLong.get() > 3) {
            // 判断图形验证码是否存在
            if (imageCode == null){
                throw new RuntimeException("图形验证码不能为空");
            }
            RBucket<String> bucket = redissonClient.getBucket("imageCode-" + loginName);
            if (!bucket.isExists()) {
                throw new RuntimeException("图形验证码已过期");
            }
            if (!imageCode.equals(bucket.get())) {
                throw new RuntimeException("图形验证码输入错误");
            }
        }

        Validate validate;
        if (isEmail(loginName)) {
            validate = validateHolder.getValidate(ValidateEnum.EMAIL.name());
        } else {
            validate = validateHolder.getValidate(ValidateEnum.ALIYUN_SMS.name());
        }
        return ResponseResult.success(validate.sendCode(loginName));
    }

    /**
     * 用户注册apiKey
     * @param loginName
     * @param userName
     * @return
     */
    @PostMapping("/register")
    public ResponseResult<ApiUser> register(
            @MyRequestBody String loginName,
            @MyRequestBody String userName,
            @MyRequestBody String code) {

        // 检查验证码是否正确
        Validate validate;
        if (isEmail(loginName)) {
            validate = validateHolder.getValidate(ValidateEnum.EMAIL.name());
        } else {
            validate = validateHolder.getValidate(ValidateEnum.ALIYUN_SMS.name());
        }
        validate.verifyCode(loginName, code);
        return ResponseResult.success(apiService.register(loginName, userName));
    }

    /**
     * 获取图形验证码
     * @return
     */
    @PostMapping("/getImageCode")
    public ResponseResult<String> getImageCode(@MyRequestBody String loginName) {
        String imageCode = ImageCodeUtil.getCode();
        RBucket<String> bucket = redissonClient.getBucket("imageCode-" + loginName);
        bucket.set(imageCode);
        // 设置超时时间
        bucket.expire(2, TimeUnit.MINUTES);
        // 获取图形验证码
        BufferedImage codeImage = ImageCodeUtil.getCodeImage(imageCode);
        // 转成base64
        String base64Image = ImageCodeUtil.getBase64Image(codeImage);

        return ResponseResult.success(base64Image);
    }

    private Boolean isEmail(String loginName) {
        // 检查用户名是否符合邮箱的格式
        String emailRegex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
        return loginName.matches(emailRegex);
    }

(2) service:

    /**
     * 用户注册apiKey
     * @param loginName
     * @param userName
     * @return
     */
    @Override
    @Transactional
    public ApiUser register(String loginName, String userName) {
        ApiUser user = apiUserMapper.selectOne(new QueryWrapper<ApiUser>().eq("login_name", loginName).eq("delete_flag",1));
        if (user != null) {
            throw new RuntimeException("用户已注册,请勿重复注册");
        }

        ApiUser apiUser = new ApiUser();
        // 生成apiKey
        String apiKey = UUID.randomUUID().toString();
        apiUser.setApiKey(apiKey);
        apiUser.setUserName(userName);
        apiUser.setLoginName(loginName);
        apiUser.setCreateTime(new Date());
        apiUser.setUpdateTime(new Date());

        // 将用户信息存入数据库
        int insert = apiUserMapper.insert(apiUser);
        if (insert < 1) {
            throw new RuntimeException("注册失败,请重试!");
        }
        return apiUser;
    }
}

(3) ValidateHolder:

/**
 * ValidateHolder组件类
 * @author CleloGauss
 */
@Component
public class ValidateHolder {

    /**
     * 用于存储验证器实例 key:名称 value:实例
     */
    private Map<String, Validate> map = new HashMap<>();

    @Resource
    private AliyunValidate aliyunValidate;

    @Resource
    private EmailValidate emailValidate;

    @PostConstruct
    private void initialize() {
        map.put(ValidateEnum.ALIYUN_SMS.name(), aliyunValidate);
        map.put(ValidateEnum.EMAIL.name(), emailValidate);
    }

    /**
     * 存储验证器
     * @param validateName
     * @param validate
     */
    public void setValidate(String validateName, Validate validate) {
        map.put(validateName, validate);
    }

    /**
     * 获取不同验证器
     * @param validateName
     * @return
     */
    public Validate getValidate(String validateName) {
        return map.get(validateName);
    }

}

(4) Validate:

/**
 * 验证器(短信、邮箱)接口
 * @author CleloGauss
 */
public interface Validate {

    /**
     * 发送验证码
     * @return
     */
    Boolean sendCode(String loginName);

    /**
     * 检验验证码
     * @param code
     * @return
     */
    void verifyCode(String loginName, String code);

    /**
     * 生成验证码
     * @return
     */
    String getCode();
}

(5) 验证器:

/**
 * 验证器(阿里云)
 * @author CleloGauss
 */
@Component("aliyunValidate")
public class AliyunValidate implements Validate {

    // 下面的配置都可放入nacos配置文件中
    public static String endpoint="dysmsapi.aliyuncs.com";
    public static String regionId="cn-hangzhou";//机房信息,可以不用更改
    public static String accessKey="";//需要修改
    public static String accessSecret="";//需要修改
    public static String sms_code="SMS_41635111";//需要修改

    public static String signName = ""; // 签名
    public static String templateCode = ""; // 模版编码

    @Resource
    private RedissonClient redissonClient;

    /**
     * 使用AK&SK初始化账号Client
     * @param accessKey
     * @param accessSecret
     * @return
     * @throws Exception
     */
    private static Client createClient(String accessKey, String accessSecret) throws Exception {
        Config config = new Config().setAccessKeyId(accessKey).setAccessKeySecret(accessSecret);
        config.endpoint = endpoint;
        config.setRegionId(regionId);
        return new Client(config);
    }

    /**
     * 发送验证码给用户
     * @param phoneNumber
     */
    @Override
    public Boolean sendCode(String phoneNumber) {
        Client client;
        try {
            client = createClient(accessKey, accessSecret);
        } catch (Exception e) {
            throw new RuntimeException("创建Client失败");
        }
        SendSmsRequest sendSmsRequest = new SendSmsRequest();
        // 设置要发送的号码、验证码、签名
        String code = getCode();
        sendSmsRequest.setPhoneNumbers(phoneNumber)
                .setSignName(signName)
                .setTemplateCode(templateCode)
                .setTemplateParam("{\"code\":"+code+"}");
        try {
            // 发送验证码
            client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions());
        } catch (Exception e) {
            throw new RuntimeException("手机验证码发送失败");
        }
        return true;
    }
    @Override
    public void verifyCode(String loginName, String code) {
        // 判断验证码的正确性
        RBucket<String> bucket = redissonClient.getBucket("verifyCode-" + loginName);
        if (!bucket.isExists()) {
            throw new RuntimeException("验证码已过期,请重新发送!");
        }
        if (!bucket.get().equals(code)){
            throw new RuntimeException("验证码错误!");
        }
    }

    /**
     * 随机生成验证码(六位)
     * @return
     */
    @Override
    public String getCode() {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for(int i=0; i<6; i++){
            int digit = random.nextInt(9) + 1;
            sb.append(digit);
        }
        return sb.toString();
    }

}
/**
 * 验证器(邮箱)
 * @author CleloGauss
 */
@Component("emailValidate")
public class EmailValidate implements Validate {
    @Resource
    private JavaMailSender javaMailSender;

    @Resource
    private RedissonClient redissonClient;

    @Override
    public Boolean sendCode(String loginName) {
        String code = getCode();

        // 将验证码放入缓存
        RBucket<String> bucket = redissonClient.getBucket("verifyCode-" + loginName);
        bucket.set(code);
        // 设置超时时间
        bucket.expire(5, TimeUnit.MINUTES);

        // 邮件对象(邮件模板,根据自身业务修改)
        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject("**apiKey注册邮箱验证码");
        message.setText("尊敬的用户您好!\n\n感谢您注册apiKey。\n\n尊敬的: " + loginName + "您的校验验证码为: " + code + ",有效期5分钟,请不要把验证码信息泄露给其他人,如非本人请勿操作");
        message.setTo(loginName);

        try {
            // 对方看到的发送人(发件人的邮箱,根据实际业务进行修改,一般填写的是企业邮箱)
            message.setFrom(new InternetAddress(MimeUtility.encodeText("dsajkl") + "<114***@qq.com>").toString());
            // 发送邮件
            javaMailSender.send(message);
        } catch (Exception e) {
            throw new RuntimeException("邮箱验证码发送失败");
        }
        return true;
    }

    @Override
    public void verifyCode(String loginName, String code) {
        // 判断验证码的正确性
        RBucket<String> bucket = redissonClient.getBucket("verifyCode-" + loginName);
        if (!bucket.isExists()) {
            throw new RuntimeException("验证码已过期,请重新发送!");
        }
        if (!bucket.get().equals(code)){
            throw new RuntimeException("验证码错误!");
        }
    }


    /**
     * 随机生成验证码(六位)
     * @return
     */
    @Override
    public String getCode() {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for(int i=0; i<6; i++){
            int digit = random.nextInt(9) + 1;
            sb.append(digit);
        }
        return sb.toString();
    }
}

(6)验证器枚举:

public enum ValidateEnum {
    EMAIL("emailValidate"),
    ALIYUN_SMS("aliyunValidate");

    private String validate;

    ValidateEnum(String validate) {
        this.validate = validate;
    }
}

(7)图形验证码工具类:

/**
 * 图形验证码工具类
 * @author CleloGauss
 */
public class ImageCodeUtil {

    /**
     * 验证码来源
     */
    private final static char[] charSet = {
            '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'
    };

    /**
     * 验证码长度
     * 默认4个字符
     */
    private final static int codeLen = 4;

    /**
     * 验证码图片字体大小
     * 默认17
     */
    private final static int fontsize = 21;

    /**
     * 验证码图片宽度
     */
    private final static int width = (fontsize+1)*codeLen+10;

    /**
     * 验证码图片高度
     */
    private final static int height = fontsize+12;

    /**
     * 干扰线条数
     */
    private final static int disturbLine = 3;

    /**
     * 字体
     */
    private final static String[] fontNames = new String[]{"黑体", "宋体", "Courier", "Arial", "Verdana", "Times", "Tahoma", "Georgia"};

    private final static int[] fontStyles = new int[]{Font.BOLD, Font.ITALIC|Font.BOLD};

    /**
     * 获取验证码(4位)
     * @return
     */
    public static String getCode() {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for(int i=0; i<codeLen; i++){
            int index = random.nextInt(charSet.length);
            sb.append(charSet[index]);
        }
        return sb.toString();
    }

    /**
     * 获取图形验证码
     * @param code
     * @return
     */
    public static BufferedImage getCodeImage(String code) {
        // 创建验证码图片
        BufferedImage codeImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);

        // 填充背景色
        Graphics g = codeImage.getGraphics();
        g.setColor(new Color(246, 240, 250));
        g.fillRect(0,0,width,height);

        // 添加干扰线条
        drawDisturbLine(g);

        // 在图片上画验证码
        Random ran = new Random();
        for (int i = 0; i < code.length(); i++) {
            // 设置字体
            g.setFont(new Font(
                    fontNames[ran.nextInt(fontNames.length)],
                    fontStyles[ran.nextInt(fontStyles.length)],
                    fontsize));
            g.setColor(getRandomColor());
            // 画验证码
            g.drawString(code.charAt(i)+"", i*fontsize+10, fontsize+5);
        }

        // 释放相关资源
        g.dispose();
        return codeImage;
    }

    /**
     * 将图形验证码转成base64
     * @param codeImage
     * @return
     */
    public static String getBase64Image(BufferedImage codeImage) {
        String base64Image;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ImageIO.write(codeImage, "gif", bos);
            byte[] imageBytes = bos.toByteArray();
            base64Image = Base64.getEncoder().encodeToString(imageBytes);
        } catch (IOException e) {
            throw new RuntimeException("获取失败");
        }
        return "data:image/gif;base64,"+base64Image;
    }

    /**
     * 为图片添加干扰线
     * @param g
     */
    private static void drawDisturbLine(Graphics g) {
        Random ran = new Random();
        for(int i = 0; i < disturbLine; i++){
            int x1 = ran.nextInt(width);
            int y1 = ran.nextInt(height);
            int x2 = ran.nextInt(width);
            int y2 = ran.nextInt(height);
            g.setColor(getRandomColor());
            //画干扰线
            g.drawLine(x1, y1, x2, y2);
        }
    }

    /**
     * 返回随机颜色
     * @return
     */
    private static Color getRandomColor() {
        Random ran = new Random();
        return new Color(ran.nextInt(220), ran.nextInt(220), ran.nextInt(220));
    }
}

(8) 相关依赖:

		<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>${redisson.version}</version>
        </dependency>

        <!--阿里云短信发送-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dysmsapi20170525</artifactId>
            <version>2.0.24</version>
        </dependency>

        <!--mail邮件发送-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

(9)相关配置:

spring:
  mail:
    host: smtp.qq.com
    username: 114****@qq.com
    password: (自己去申请:QQ邮箱->设置->账号安全->申请授权码)
    default-encoding: utf-8
    properties:
      mail:
        smtp:
          port: 465
          ssl:
            enable: true
            required: true
          timeout: 10000
          connection-timeout: 10000
          write-timeout: 10000

4、后续:

如果需要自己写其他平台的短信发送,可调用 ValidateHolder 中的 set 方法,编写自己的验证器,思路都一样。短信验证都需要去平台中开通短信服务,邮箱验证需要开启授权,图形验证码的核心都在工具类中。

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java实现图形验证码的步骤如下: 1. 生成随机字符串:可以使用Java内置的随机数生成器或者第三方库生成随机字符串,例如Apache Commons Lang中的RandomStringUtils。 2. 生成验证码图片:可以使用Java内置的图形处理库或者第三方库生成图片。具体实现方法可以参考Java官方文档或者第三方库的文档。 3. 将随机字符串存储到Session中:在生成验证码图片的同时,将生成的随机字符串存储到Session中,以便后续验证用户输入的验证码是否正确。 4. 输出验证码图片:将生成的验证码图片输出到前端页面,让用户进行输入验证。 5. 验证用户输入的验证码:当用户提交表单时,从Session中取出生成的随机字符串,与用户输入的验证码进行比较,以验证用户输入的验证码是否正确。 示例代码如下: ```java // 生成随机字符串 String randomStr = RandomStringUtils.randomAlphanumeric(6); // 将随机字符串存储到Session中 request.getSession().setAttribute("captcha", randomStr); // 生成验证码图片 BufferedImage image = new BufferedImage(100, 30, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, 100, 30); g.setColor(Color.BLACK); g.setFont(new Font("Arial", Font.BOLD, 20)); g.drawString(randomStr, 10, 20); g.dispose(); // 输出验证码图片 response.setContentType("image/jpeg"); OutputStream out = response.getOutputStream(); ImageIO.write(image, "jpeg", out); out.close(); // 验证用户输入的验证码 String captcha = (String) request.getSession().getAttribute("captcha"); if (captcha != null && captcha.equalsIgnoreCase(userInput)) { // 验证码正确 } else { // 验证码错误 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值