1024电商项目的邮箱验证码与图形验证码功能模块
項目基于springcloudalibaba,模塊功能大致概括就是登錄頁面的時候先完成圖形驗證碼的校驗,輸入的數(shù)字和字母與圖片上的相對應之后,會向對應的郵箱或手機號發(fā)送郵箱/短信驗證碼二次驗證。這里展示的是郵箱驗證碼。
用到的技術點有:基于SpringCloudAlibaba框架+redis緩存+swagger開發(fā)文檔
首先要在common項目中封裝一些通用模塊的工具類與枚舉類(用于生成隨機數(shù)與驗證碼)以及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發(fā)布.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發(fā)布.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();}/*** 配置全局通用參數(shù), 支持配置多個響應參數(shù)* @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("請求錯誤,根據(jù)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.獲取驗證碼的隨機數(shù)
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")) {// 根據(jù)網(wǎng)卡取本機配置的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;}/*** 獲取驗證碼隨機數(shù)* @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狀態(tài)碼:
package net.xdclass.util;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import net.xdclass.enums.BizCodeEnum;@Data @AllArgsConstructor @NoArgsConstructor public class JsonData {/*** 狀態(tài)碼 0 表示成功,1表示處理中,-1表示失敗*/private Integer code;/*** 數(shù)據(jù)*/private Object data;/*** 描述*/private String msg;/*** 成功,傳入數(shù)據(jù)* @return*/public static JsonData buildSuccess() {return new JsonData(0, null, null);}/*** 成功,傳入數(shù)據(jù)* @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);}/*** 自定義狀態(tài)碼和錯誤信息* @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服務中創(chuàng)建此服務需要的配置(驗證碼的樣式:干擾線,字體間隔,文本來源,圖片樣式):
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");//驗證碼個數(shù)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");//干擾實現(xiàn)類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:發(fā)送郵箱驗證碼 3.獲取緩存的key
1.獲取圖形驗證碼需要存儲在redis中,這里提前在linux服務器上安裝并配置了redis和docker
2.發(fā)送郵箱驗證碼需要兩個步驟:1.匹配圖形驗證碼是否正確 2.發(fā)送驗證碼
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);}}/*** 發(fā)送驗證碼* 1.匹配圖形驗證碼是否正常* 2.發(fā)送驗證碼** @param to* @param captcha* @param request* @return*/@ApiOperation("發(fā)送郵箱注冊驗證碼")@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:
步驟在注釋里:前置判斷是否發(fā)送--發(fā)送驗證碼--存儲到緩存里--后置存儲發(fā)送記錄
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="項目驗證碼";/*** 驗證碼內(nèi)容*/private static final String CONTENT="您的驗證碼是%s,有效時間是60s,打死也不告訴任何人";/*** 10分鐘有效*/private static final int CODE_EXPPIRED=60*1000*10;/*** 前置:判斷是否重復發(fā)送** 1.發(fā)送驗證碼** 2.存儲到緩存** 后置:存儲發(fā)送記錄* @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秒內(nèi)重復發(fā)送if (StringUtils.isNotBlank(cacheValue)){//TODOlong ttl =Long.parseLong(cacheValue.split("_")[1]);//小于60秒,則不給重復發(fā)送if(CommonUtil.getCurrentTimestamp() - ttl<1000*60){log.info("重復發(fā)送驗證碼,時間間隔:{}",(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)){//設置驗證碼的位數(shù)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文檔:
?
?
?
總結
以上是生活随笔為你收集整理的1024电商项目的邮箱验证码与图形验证码功能模块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: android 照片拼接长图_图文长截图
- 下一篇: 网关 架构演进及实践
