1024电商项目的邮箱验证码与图形验证码功能模块

项目基于springcloudalibaba,模块功能大致概括就是登录页面的时候先完成图形验证码的校验,输入的数字和字母与图片上的相对应之后,会向对应的邮箱或手机号发送邮箱/短信验证码二次验证。这里展示的是邮箱验证码。

用到的技术点有:基于SpringCloudAlibaba框架+redis缓存+swagger开发文档

首先要在common项目中封装一些通用模块的工具类与枚举类(用于生成随机数与验证码)以及swaggerconfig直接代码展示:

SwaggerConfiguration:用于自动生成接口文档后面会展示

package net.xdclass.config;import lombok.Data;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;import java.util.ArrayList;
import java.util.List;@Component
@Data
@EnableOpenApi
public class SwaggerConfiguration {/*** 对c端用户的接口文档* @return*/@Beanpublic Docket webApiDoc() {return new Docket(DocumentationType.OAS_30).groupName("用户端接口文档").pathMapping("/")// 定义是否开启swagger,false为关闭,可以通过变量控制,线上关闭.enable(true)//配置api文档元信息.apiInfo(apiInfo())// 选择哪些接口作为swagger的doc发布.select()//扫描对应的所有包.apis(RequestHandlerSelectors.basePackage("net.xdclass"))//正则匹配请求路径,并分配至当前分组.paths(PathSelectors.ant("/api/**")).build().globalRequestParameters(getGlobalRequestParameters()).globalResponses(HttpMethod.GET,getGlobalResponseMessage()).globalResponses(HttpMethod.POST,getGlobalResponseMessage());}/*** 对管理端用户的接口文档* @return*/@Beanpublic Docket adminApiDoc() {return new Docket(DocumentationType.OAS_30).groupName("管理端接口文档").pathMapping("/")// 定义是否开启swagger,false为关闭,可以通过变量控制,线上关闭.enable(true)//配置api文档元信息.apiInfo(apiInfo())// 选择哪些接口作为swagger的doc发布.select()//扫描对应的所有包.apis(RequestHandlerSelectors.basePackage("net.xdclass"))//正则匹配请求路径,并分配至当前分组.paths(PathSelectors.ant("/admin/**")).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("1024电商平台").description("微服务接口文档").contact(new Contact("孙翊轩", "https://xdclass.net", "2026913461@qq.com")).version("v1.0").build();}/*** 配置全局通用参数, 支持配置多个响应参数* @return*/private List<RequestParameter> getGlobalRequestParameters() {List<RequestParameter> parameters = new ArrayList<>();parameters.add(new RequestParameterBuilder().name("token").description("登录令牌").in(ParameterType.HEADER).query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))).required(false).build());//        parameters.add(new RequestParameterBuilder()
//                .name("version")
//                .description("版本号")
//                .required(true)
//                .in(ParameterType.HEADER)
//                .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
//                .required(false)
//                .build());return parameters;}/*** 生成通用响应信息* @return*/private List<Response> getGlobalResponseMessage() {List<Response> responseList = new ArrayList<>();responseList.add(new ResponseBuilder().code("4xx").description("请求错误,根据code和msg检查").build());return responseList;}
}

CheckUtils正则工具类:用于邮箱或手机号的正则

package net.xdclass.util;import java.util.regex.Matcher;
import java.util.regex.Pattern;public class CheckUtil {/*** 邮箱正则*/private static final Pattern MAIL_PATTERN = Pattern.compile("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$");/*** 手机号正则,暂时未用*/private static final Pattern PHONE_PATTERN = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$");/*** @param email* @return*/public static  boolean isEmail(String email) {if (null == email || "".equals(email)) {return false;}Matcher m = MAIL_PATTERN.matcher(email);return m.matches();}/*** 暂时未用* @param phone* @return*/public static boolean isPhone(String phone) {if (null == phone || "".equals(phone)) {return false;}Matcher m = PHONE_PATTERN.matcher(phone);return m.matches();}
}

CommonUtils:1.获取ip 后面将ip做成redis的key保证不重复

2.Md5加密

3.获取验证码的随机数

4.获取当前时间戳 后面会在md5加密的验证码后面拼接时间戳

package net.xdclass.util;import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.util.Random;public class CommonUtil {/*** 获取ip* @param request* @return*/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")) {// 根据网卡取本机配置的IPInetAddress inet = null;try {inet = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}ipAddress = inet.getHostAddress();}}// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割if (ipAddress != null && ipAddress.length() > 15) {// "***.***.***.***".length()// = 15if (ipAddress.indexOf(",") > 0) {ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));}}} catch (Exception e) {ipAddress="";}return ipAddress;}/*** MD5加密* @param data* @return*/public static String MD5(String data)  {try {java.security.MessageDigest md = MessageDigest.getInstance("MD5");byte[] array = md.digest(data.getBytes("UTF-8"));StringBuilder sb = new StringBuilder();for (byte item : array) {sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));}return sb.toString().toUpperCase();} catch (Exception exception) {}return null;}/*** 获取验证码随机数* @return*/public static String getRandom(int length){String sources="0123456789";Random random =new Random();StringBuilder sb=new StringBuilder();for (int i = 0; i < length; i++) {sb.append(sources.charAt(random.nextInt(9)));}return sb.toString();}/*** 获取当前时间戳* @return*/public static long getCurrentTimestamp(){return System.currentTimeMillis();}
}

JsonData状态码:

package net.xdclass.util;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.xdclass.enums.BizCodeEnum;@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);}/***  成功,传入数据* @param data* @return*/public static JsonData buildSuccess(Object data) {return new JsonData(0, data, null);}/*** 失败,传入描述信息* @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());}
}

其次,我们要在user-service服务中创建此服务需要的配置(验证码的样式:干扰线,字体间隔,文本来源,图片样式):

package net.xdclass.config;import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Properties;@Configuration
public class CaptchaConfig {/*** 验证码配置* Kaptcha配置类名** @return*/@Bean@Qualifier("captchaProducer")public DefaultKaptcha kaptcha() {DefaultKaptcha kaptcha = new DefaultKaptcha();Properties properties = new Properties();
//    properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
//    properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "220,220,220");
//    //properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "38,29,12");
//    properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "147");
//    properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "34");
//    properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "25");
//    //properties.setProperty(Constants.KAPTCHA_SESSION_KEY, "code");//验证码个数properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
//    properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Courier");//字体间隔properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE,"8");//干扰线颜色
//    properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "white");//干扰实现类properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");//图片样式properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple");//文字来源properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789qwertyuiopasdfghjklzxcvbnm");Config config = new Config(properties);kaptcha.setConfig(config);return kaptcha;}
}

然后就可以开始正式的编写了,首先从controller层开始需要做两个接口:1.获取图形验证码 2:发送邮箱验证码 3.获取缓存的key

1.获取图形验证码需要存储在redis中,这里提前在linux服务器上安装并配置了redis和docker

2.发送邮箱验证码需要两个步骤:1.匹配图形验证码是否正确 2.发送验证码

3.将redis key-value的key设置成ip以用来防止重复

package net.xdclass.controller;import com.google.code.kaptcha.Producer;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.SendCodeEnum;
import net.xdclass.service.NotifyService;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Protocol;import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;@Api(tags = "通知模块")
@RestController
@RequestMapping("/api/user/v1")
@Slf4j
public class NotifyController {@Autowiredprivate Producer captchaProducer;@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate NotifyService notifyService;private static final long CAPTCHA_CODE_EXPIRED=60*1000*10;/*** 获取图形验证码* @param request* @param response*/@ApiOperation("获取图形验证码")@GetMapping ("captcha")public void getCaptcha(HttpServletRequest request, HttpServletResponse response){String captchaTest = captchaProducer.createText();log.info("图形验证码:{}",captchaTest);//存储redisTemplate.opsForValue().set(getCaptchaKey(request),captchaTest,CAPTCHA_CODE_EXPIRED,TimeUnit.MILLISECONDS);BufferedImage bufferedImage =captchaProducer.createImage(captchaTest);ServletOutputStream outputStream=null;try {outputStream= response.getOutputStream();ImageIO.write(bufferedImage,"jpg",outputStream);outputStream.flush();outputStream.close();}catch (IOException e){log.error("获取图形验证码异常:{}",e);}}/*** 发送验证码* 1.匹配图形验证码是否正常* 2.发送验证码** @param to* @param captcha* @param request* @return*/@ApiOperation("发送邮箱注册验证码")@GetMapping("send_code")public JsonData sendRegisterCode(@ApiParam("收信人") @RequestParam(value = "to", required = true)String to,@ApiParam("图形验证码") @RequestParam(value = "captcha", required = true)String  captcha,HttpServletRequest request){String key = getCaptchaKey(request);String cacheCaptcha = redisTemplate.opsForValue().get(key);//匹配验证码是否一样if(captcha!=null && cacheCaptcha!=null && cacheCaptcha.equalsIgnoreCase(captcha)) {//成功redisTemplate.delete(key);JsonData jsonData = notifyService.sendCode(SendCodeEnum.USER_REGISTER,to);return jsonData;}else {return JsonData.buildResult(BizCodeEnum.CODE_CAPTCHA_ERROR);}}/*** 获取缓存key* @param request* @return*/private String getCaptchaKey(HttpServletRequest request){String ip= CommonUtil.getIpAddr(request);String userAgent=request.getHeader("User-Agent");String key ="user-service:captcha:"+CommonUtil.MD5(ip+userAgent);log.info("ip={}",ip);log.info("userAgent={}",userAgent);log.info("key={}",key);return key;}}

Service:

package net.xdclass.service;import net.xdclass.enums.SendCodeEnum;
import net.xdclass.util.JsonData;public interface NotifyService {JsonData sendCode(SendCodeEnum sendCodeEnum,String to);
}

ServiceImpl:

步骤在注释里:前置判断是否发送--发送验证码--存储到缓存里--后置存储发送记录

package net.xdclass.service.impl;import lombok.extern.slf4j.Slf4j;
import net.xdclass.constant.CacheKey;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.SendCodeEnum;
import net.xdclass.service.MailService;
import net.xdclass.service.NotifyService;
import net.xdclass.util.CheckUtil;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.JsonData;
import org.apache.commons.lang3.StringUtils;
import org.mockito.internal.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
@Slf4j
public class NotifyServiceImpl implements NotifyService {@Autowiredprivate MailService mailService;@Autowiredprivate StringRedisTemplate redisTemplate;/*** 验证码标题*/private static final String SUBJECT="项目验证码";/*** 验证码内容*/private static final String CONTENT="您的验证码是%s,有效时间是60s,打死也不告诉任何人";/*** 10分钟有效*/private static final int CODE_EXPPIRED=60*1000*10;/*** 前置:判断是否重复发送** 1.发送验证码** 2.存储到缓存** 后置:存储发送记录* @param sendCodeEnum* @param to* @return*/@Overridepublic JsonData sendCode(SendCodeEnum sendCodeEnum, String to) {String cachekey =String.format(CacheKey.CHECK_CODE_KEY,sendCodeEnum.name(),to);String cacheValue= redisTemplate.opsForValue().get(cachekey);//如果不为空,则判断是否60秒内重复发送if (StringUtils.isNotBlank(cacheValue)){//TODOlong ttl =Long.parseLong(cacheValue.split("_")[1]);//小于60秒,则不给重复发送if(CommonUtil.getCurrentTimestamp() - ttl<1000*60){log.info("重复发送验证码,时间间隔:{}",(CommonUtil.getCurrentTimestamp()-ttl)/1000);return JsonData.buildResult(BizCodeEnum.CODE_LIMITED);}}//拼接验证码 2233_32131231String code=CommonUtil.getRandom(6);String value=code+"_"+CommonUtil.getCurrentTimestamp();redisTemplate.opsForValue().set(cachekey,value,CODE_EXPPIRED, TimeUnit.MILLISECONDS);if(CheckUtil.isEmail(to)){//设置验证码的位数mailService.sendMail(to,SUBJECT,String.format(CONTENT,code));return JsonData.buildSuccess();}else if (CheckUtil.isPhone(to)){}return JsonData.buildResult(BizCodeEnum.CODE_TO_ERROR);}
}

效果展示:

 

 登录到1156571678@qq.com查看

 展示swagger文档:

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/419818.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android自己的自动化测试Monkeyrunner和用法示例

眼下android SDK在配有现成的测试工具monkey 和 monkeyrunner两。也许我们不看一样的兄弟名字。但事实是完全跑了两个完全不同的工具。在测试的不同区域的应用程序。总体&#xff0c;monkey主要用于压力和可靠性测试&#xff0c;拟键盘事件流。而且能够自定义发送的次数&#x…

内存大对象dump linux_在 Linux 上创建并调试转储文件 | Linux 中国

了解如何处理转储文件将帮你找到应用中难以重现的 bug。• 来源&#xff1a;linux.cn • 作者&#xff1a;Stephan Avenwedde • 译者&#xff1a;Xingyu.Wang •(本文字数&#xff1a;5501&#xff0c;阅读时长大约&#xff1a;6 分钟)崩溃转储、内存转储、核心转储、系统转储…

爬虫用mysql存储还是mongodb_【面试题】Mongodb和MySQL存储爬虫数据的特点是什么?...

(1)问题分析面试官主要考核Mongodb和MySQL数据库的特点&#xff0c;以及关系型与非关系型数据库。(2)核心问题讲解MySQL属于关系型数据库&#xff0c;它具有以下特点&#xff1a;在不同的引擎上有不同的存储方式。查询语句是使用传统的sql语句&#xff0c;拥有较为成熟的体系&a…

mysql originator_MySQL数据库事件调度(Event)

4.创建事件调度每5秒在表中插入数据MySQL> create event if not exists event_t1 on schedule every 5 second do insert into t values(1,1,sysdate());Query OK, 0 rows affected (0.01 sec)mysql> select * from t; --查看事件执行数据-------------------…

前端学习(1520):vue-router嵌套路由

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title></head> <!-- 1引入…

python与sql连接不上_Python连接不上SQL Server的两种根治思路

连接不上数据库&#xff0c;首先可以排除是代码的问题&#xff0c;连接方式都是千篇一律的。大多数问题都是本机的两个原因造成的&#xff0c;1.服务没有开启,2.没有启动SQL配置的TCP/IP下面给出统一解决方案&#xff1a;首先从开始菜单找到SQL数据库的配置工具&#xff0c;比如…

python多进程和多线程一起使用_Python:多进程和多线程

在现实社会&#xff0c;我们经常需要一种场景&#xff0c;就是同时有多个事情需要执行&#xff0c;如在浏览网页的同时需要听音乐。比如说在跳舞的时候要唱歌。同样的&#xff0c;在程序中我们也可能需要这种场景。如下面我们以同时听音乐和浏览网页为例。def network():while …

python字典怎么换行_Python字典如何换行

原标题&#xff1a;Python字典如何换行Python字典换行的方法如下&#xff1a;1、换行时保证行尾是逗号即可a {"key0": "val0","key1": "val1","key2": "val2"}2、在长度不影响阅读的情况下这种写法也是允许的&am…

回复《我要阻止做java开发的男朋友去创业型公司工作吗?》园友问题

真的非常开心能收到这么多园友的关心&#xff0c;看到这么多的回复顿感身边处处充满爱。也非常感谢大家踊跃的帮我出谋划策&#xff0c;小女子在此有礼了&#xff01; 我先来回答一下性别的问题&#xff08;前面已经暴露了……&#xff09;&#xff0c;我是前端程序媛。大三时和…

java date转sql date_java.util.Date和java.sql.Date转换(转)

Date 的类型转换&#xff1a;首先记住java.util.Date 为 java.sql.Date的父类1.将java.util.Date 转换为 java.sql.Datejava.lang.ClassCastException: java.util.Date cannot be cast to java.sql.DateDate dnew Date(); //java.util.Datenew java.sql.Date(d.getTime()) //…

Kaggle Bike Sharing Demand Prediction – How I got in top 5 percentile of participants?

Kaggle Bike Sharing Demand Prediction – How I got in top 5 percentile of participants? Introduction There are three types of people who take part in a Kaggle Competition: Type 1: Who are experts in machine learning and their motivation is to compete with…

java 组合对象_Java 中组合模型之对象结构模式的详解

Java 中组合模型之对象结构模式的详解一、意图将对象组合成树形结构以表示”部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。二、适用性你想表示对象的部分-整体层次结构你希望用户忽略组合对象与单个对象的不同&#xff0c;用户将统一使用组…

前端学习(1525):简化模板代码

app.vue <template><div id"app"></div> </template><script> export default {name: app,data () {return {}} } </script><style>运行结果

java 整型长度_java int的长度是多少

int&#xff1a;int 数据类型是32位、有符号的以二进制补码表示的整数&#xff1b; (推荐学习&#xff1a;java课程)最小值是 -2,147,483,648(-2^31)&#xff1b;最大值是 2,147,483,647(2^31 - 1)&#xff1b;一般地整型变量默认为 int 类型&#xff1b;默认值是 0 &#xff1…