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 分钟)崩溃转储、内存转储、核心转储、系统转储…

关于软件工程的那些事儿————《人·绩效·职业道德》和《一个程序员的生命周期》读后感...

对于这片文章&#xff0c;首先标题很亮眼--------“人绩效职业道德”。 首先&#xff0c;是“人”。我个人的理解&#xff0c;“人”在团队中有着重要的作用。一个人不可能独立的存在&#xff0c;无论是在学习工作还是生活中&#xff0c;都需要与他人的合作才能完成每项任务。通…

爬虫用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; --查看事件执行数据-------------------…

你的灯亮着么阅读笔记3

第五章问题从哪来的。我们要探寻问题的根源&#xff0c;而问题的根源往往出现在自身。因此我们要时常的反思自己&#xff0c;是否在团队中拖了后腿&#xff0c;而不是一味的去指责队友对自己的埋怨。只有发现自身的问题&#xff0c;才能改正自我。 第六章我们真的想解决问题么。…

python web 框架的flash消息_Flask flash 消息闪现

浮生梦&#xff0c;三生渺渺&#xff0c;因缘无踪&#xff0c;虽堪恋&#xff0c;何必重逢。息壤生生&#xff0c;谁当逝水&#xff0c;东流无终。flash传递与获取数据#coding:utf-8# flash是生成传递数据&#xff0c;get_flashed_messages是获取传递过来的数据from flask impo…

OC-NSFileManager和NSFileHandle的使用

对于objective-c中的有关文件目录的操作和文件的操作。 通过一道习题来熟悉NSFileManager和NSFileHandle中的方法的使用。 本题原意:将指定目录下所有后缀名为XXX(可以自己输入)的文件中字符串为"him"(可以自己输入)改为"me"(可以自己输入) 1.首先新建了一…

前端学习(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;比如…

Bootstrap系列 -- 23. 图片

图像在网页制作中也是常要用到的元素&#xff0c;在Bootstrap框架中对于图像的样式风格提供以下几种风格&#xff1a; 1、img-responsive&#xff1a;响应式图片&#xff0c;主要针对于响应式设计  2、img-rounded&#xff1a;圆角图片  3、img-circle&#xff1a;圆形图片…

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

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

Java多线程异常处理

在java多线程程序中&#xff0c;所有线程都不允许抛出未捕获的checked exception&#xff0c;也就是说各个线程需要自己把自己的checked exception处理掉。这一点是通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throws exception部分)进行了约束。但是线程依然有…

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…