SpringBoot整合图形验证码和邮箱验证码进行注册

一. 图形验证码



1. 引入图形验证码依赖

<!--图形验证码-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>kaptcha-spring-boot-starter</artifactId>
</dependency>

2. 编写生成图形验证码业务代码

@Slf4j
@Api(tags = "通知模块")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/notify")
public class NotifyController {

	private final Producer captchaProducer;
	private final StringRedisTemplate redisTemplate;

	private static final long CAPTCHA_CODE_EXPIRED = 60 * 1000 * 10; // 10 mins

	@ApiOperation("获取验证码")
	@GetMapping("/captcha")
	public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {

		// 生成文字验证码
		String text = captchaProducer.createText();
		log.info("图形验证码:{}", text);

		// 存入redis
		redisTemplate.opsForValue().set(getCaptchKey(request), text, CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS);

		// 生成图片验证码
		BufferedImage image = captchaProducer.createImage(text);
		ServletOutputStream outputStream = null;

		try {
			outputStream = response.getOutputStream();
			ImageIO.write(image, "jpg", outputStream);
			outputStream.flush();
			outputStream.close();
		} catch (IOException e) {
			log.error("图形验证码生成失败", e);
		}
	}

	/** 获取缓存的Key */
	private String getCaptchKey(HttpServletRequest request){

		String ip = CommonUtil.getIpAddr(request);
		String userAgent = request.getHeader("User-Agent");

		String key = "user-service:captcha:" + SecureUtil.md5(ip + userAgent);
		log.info("ip:{},userAgent:{},key:{}",ip,userAgent,key);

		return key;
	}
	
}

获取IP地址的工具类如下, MD5使用的是 hutool 包里面的加密工具类 SecureUtil

public static String getIpAddr(HttpServletRequest request) {

	String ipAddress = null;
	try {
		ipAddress = request.getHeader("x-forwarded-for");
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("Proxy-Client-IP");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getRemoteAddr();
			if (ipAddress.equals("127.0.0.1")) {
				// 根据网卡取本机配置的IP
				InetAddress inet = null;
				try {
					inet = InetAddress.getLocalHost();
				} catch (UnknownHostException e) {
					e.printStackTrace();
				}
				ipAddress = inet.getHostAddress();
			}
		}
		
		// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
		if (ipAddress != null && ipAddress.length() > 15) {
			// "***.***.***.***".length()
			// = 15
			if (ipAddress.indexOf(",") > 0) {
				ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
			}
		}
		
	} catch (Exception e) {
		ipAddress="";
	}
	return ipAddress;
}

二. 邮箱验证码



1. 引入邮箱验证码依赖

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

2. 配置 yml 配置文件

把下面的一些配置改成你自己的

  #邮箱服务配置
  mail:
    host: smtp.163.com #发送邮件服务器
    username: 188xxxxxxxx@163.com #发送邮件的邮箱地址
    password: HDQTxxxxxxxxxxxx #客户端授权码,不是邮箱密码,网易的是自己设置的
    from: 188xxxxxxxx@163.com # 发送邮件的地址,和上面username一致
    properties.mail.smtp.starttls.enable: true
    properties.mail.smtp.starttls.required: true
    properties.mail.smtp.ssl.enable: true
    default-encoding: utf-8

3. 编写邮件发送代码

public class MailServiceImpl implements MailService {

	@Resource
	private JavaMailSender mailSender;

	@Value("${spring.mail.from}")
	private String from;

	@Override
	public void sendMail(String to, String subject, String content) {
	
		SimpleMailMessage message = new SimpleMailMessage();
		
        // 发件人
		message.setFrom(from);
        // 收件人
		message.setTo(to);
        // 邮件主题
		message.setSubject(subject);
        // 邮件内容
		message.setText(content);
        // 发送邮件
		mailSender.send(message);
		log.info("邮件发送成功:{}", message);
		
	}
}

4. 发送验证码

其中的 JsonData 为统一响应结果,
BizCodeEnum 为错误码枚举值,后面也会给出
可以换成你自己的,也可以复制我的,具体代码在下面

controller 层初步校验图形验证码是否正确, 正确之后再执行发送邮件操作

/**
 * @author Ccoo
 * 2024/2/24
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonData {

	/**
	 * 状态码 0 表示成功,1表示处理中,-1表示失败
	 */

	private Integer code;
	/**
	 * 数据
	 */
	private Object data;
	/**
	 * 描述
	 */
	private String msg;

	/**
	 * 成功,传入数据
	 * @return
	 */
	public static JsonData buildSuccess() {
		return new JsonData(0, null, null);
	}

	/**
	 * 成功,传入数据
	 * @return
	 */
	public static JsonData buildSuccess(String msg) {
		return new JsonData(0, null, msg);
	}

	/**
	 *  成功,传入数据
	 * @param data
	 * @return
	 */
	public static JsonData buildSuccess(Object data) {
		return new JsonData(0, data, null);
	}

	/**
	 *  成功,传入数据
	 * @param data
	 * @return
	 */
	public static JsonData buildSuccess(Object data, String msg) {
		return new JsonData(0, data, msg);
	}

	/**
	 * 失败,传入描述信息
	 * @param msg
	 * @return
	 */
	public static JsonData buildError(String msg) {
		return new JsonData(-1, null, msg);
	}

	/**
	 * 自定义状态码和错误信息
	 * @param code
	 * @param msg
	 * @return
	 */
	public static JsonData buildCodeAndMsg(int code, String msg) {
		return new JsonData(code, null, msg);
	}

	/**
	 * 传入枚举,返回信息
	 * @param codeEnum
	 * @return
	 */
	public static JsonData buildResult(BizCodeEnum codeEnum){
		return JsonData.buildCodeAndMsg(codeEnum.getCode(),codeEnum.getMessage());
	}

}

/**
 * @author Ccoo
 * 2024/2/24
 */
@Getter
public enum BizCodeEnum {

	/**
	 * 通用操作码
	 */
	OPS_REPEAT(110001,"重复操作"),

	/**
	 *验证码
	 */
	CODE_TO_ERROR(240001,"接收号码不合规"),
	CODE_LIMITED(240002,"验证码发送过快"),
	CODE_ERROR(240003,"验证码错误"),
	CODE_CAPTCHA(240101,"图形验证码错误"),
	CODE_CAPTCHA_ERROR(240102,"邮箱验证码错误"),
	CODE_SEND_SUCCESS(240103,"验证码发送成功"),

	/**
	 * 账号
	 */
	ACCOUNT_REPEAT(250001,"账号已经存在"),
	ACCOUNT_UNREGISTER(250002,"账号不存在"),
	ACCOUNT_PWD_ERROR(250003,"账号或者密码错误"),
	ACCOUNT_UNLOGIN(250004,"账号未登录");

	private final int code;
	private final String message;

	BizCodeEnum(int code, String message) {
		this.code = code;
		this.message = message;
	}

}

@ApiOperation("发送验证码")
@PostMapping("/send_code")
public JsonData sendRegisterCode(SendCodeRequest sendCodeRequest, HttpServletRequest request) {

	// 校验图形验证码
	String captchaKey = getCaptchKey(request);
	String cacheCaptcah = redisTemplate.opsForValue().get(captchaKey);
	
	// 校验验证码是否正确
	if(ObjectUtil.isEmpty(sendCodeRequest) || !sendCodeRequest.getCaptcha().equalsIgnoreCase(cacheCaptcah)){
		return JsonData.buildResult(BizCodeEnum.CODE_CAPTCHA_ERROR);
	}
	
	// 删除图形验证码
	redisTemplate.delete(captchaKey);
	// 发送验证码
	return notifyService.sendCode(SendCodeEnum.USER_REGISTER, sendCodeRequest.getTo());
}

获取缓存中验证码, 有则判断是否60S内重复发送, 无则拼接验证码并写入缓存
邮箱验证码在缓存中的Key格式为 code : 注册类型 : 收信人方式
比如 : code:USER_REGISTER:257xxxxxxx@qq.com
RandomUtil 也是 Hutool 工具包里面的

private static final String SUBJECT = "莜莜专属注册验证码";
private static final String CONTENT = "你的验证码是: %s, 有效时间为60秒, 不要告诉别人哦~";
private static final int CODE_EXPIRED = 60 * 1000 * 10; // 10mins

/**
 * 发送验证码
 * @param sendCodeEnum 验证码类型
 * @param to 发送对象
 * @return JsonData
 */
@Override
public JsonData sendCode(SendCodeEnum sendCodeEnum, String to) {

	// 获取缓存中的验证码
	String cacheKey = String.format(CacheKey.CHECK_CODE_KEY, sendCodeEnum.name(), to);
	String cacheValue = restTemplate.opsForValue().get(cacheKey);
	// 如果缓存中有值, 判断是否重复发送
	if(StrUtil.isNotBlank(cacheValue)){
		long ttl = Long.parseLong(cacheValue.split("_")[1]);
		if(System.currentTimeMillis() - ttl < 60 * 1000){
			log.info("验证码发送过快, 时间间隔:{}秒", (System.currentTimeMillis() - ttl) / 1000);
			return JsonData.buildResult(BizCodeEnum.CODE_LIMITED);
		}
	}
	
	// 拼接验证码 (验证码+时间戳)
	String code = RandomUtil.randomNumbers(4);
	String value = code + "_" + System.currentTimeMillis();
	// 保存验证码到缓存中
	restTemplate.opsForValue().set(cacheKey, value, CODE_EXPIRED, TimeUnit.MILLISECONDS);
	// 发送邮箱验证码
	if(Validator.isEmail(to)){
		mailService.sendMail(to, SUBJECT, String.format(CONTENT, code));
		return JsonData.buildResult(BizCodeEnum.CODE_SEND_SUCCESS);
	}

	return JsonData.buildResult(BizCodeEnum.CODE_TO_ERROR);
}

本期内容分享到此结束啦~

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值