2026/1/3 3:01:08
网站建设
项目流程
自己做企业网站详细流程免费,网络营销的主要工具有哪些,企业网站制作心得,零基础学wordpress pdf下载在白嫖之前#xff0c;希望你会内疚#xff0c;最起码点个赞收藏再自取吧#xff0c;源码在最后#xff0c;自取#xff1b;
在白嫖之前#xff0c;希望你会内疚#xff0c;最起码点个赞收藏再自取吧#xff0c;源码在最后#xff0c;自取#xff1b;
在白嫖之前希望你会内疚最起码点个赞收藏再自取吧源码在最后自取在白嫖之前希望你会内疚最起码点个赞收藏再自取吧源码在最后自取在白嫖之前希望你会内疚最起码点个赞收藏再自取吧源码在最后自取总结前端触发登录流程调用/phoneCaptcha接口并传入account查询对应的User和Role信息封装包含手机号和是否需要验证码isVerify的PhoneAndCaptchaVO返回给前端若isVerify为1前端调用/captcha接口并传入手机号先校验手机号格式合法则生成6位随机验证码将验证码存入Redis并设置60秒过期再调用短信服务发送对应模板的验证码最后返回“发送成功”提示前端输入验证码后调用/verifyCaptcha接口传入手机号和验证码先校验两者格式合法则从Redis读取缓存的验证码若验证码存在且匹配删除Redis中的验证码并返回true以继续登录若不存在或不匹配则记录失败日志并返回false提示用户若isVerify不为1则直接走后续登录流程。一、功能实现思路该功能围绕角色登录的验证码流程展开核心包含「生成验证码、验证验证码、登录前置校验判断是否需要验证码」三个核心接口整体遵循「参数校验→核心逻辑→结果返回」的分层设计思路具体拆解如下1. 核心流程总览前端触发登录 → 调用/phoneCaptcha接口传入账号→ 获取手机号是否需要验证码 → ├─ 若需要验证码 → 调用/captcha接口传入手机号→ 生成并发送短信验证码 → │ 前端输入验证码 → 调用/verifyCaptcha接口 → 校验验证码有效性 → 返回校验结果 └─ 若不需要验证码 → 直接走后续登录逻辑2. 各模块具体实现思路模块/接口核心逻辑技术选型/设计细节/phoneCaptcha登录前置校验根据账号查询用户角色信息返回「手机号是否需要验证码」1. 基于MyBatis-Plus的lambdaQuery查询用户/角色2. 封装VO对象统一返回格式3. 空值兼容用户/角色为空时VO字段默认null/captcha生成并发送短信验证码同时缓存到Redis1. 手机号格式校验PhoneUtil2. 生成6位随机数RandomUtil3. Redis设置60秒过期防止验证码长期有效4. 调用短信服务发送验证码5. 异常统一封装为ServiceException/verifyCaptcha校验用户输入的验证码是否有效1. 格式校验手机号6位数字验证码2. 从Redis读取缓存的验证码3. 匹配成功后删除Redis验证码防止重复使用4. 日志记录校验结果便于排查问题枚举/TemplateCodeEnum统一管理短信模板CODE避免硬编码1. 封装模板ID/CODE/描述2. 提供根据CODE查询枚举的方法增强可读性和维护性3. 分层设计Controller层仅负责接收参数、调用Service、统一返回R格式结果Blade框架的通用响应体无业务逻辑Service层封装核心业务逻辑校验、Redis操作、短信发送、数据库查询异常统一抛出ServiceExceptionVO层PhoneAndCaptchaVO封装前端所需的手机号和是否需要验证码字段符合接口返回的结构化要求枚举层TemplateCodeEnum统一管理短信模板降低硬编码风险。二、功能重难点分析1. 核心重点保证功能可用、安全、稳定1验证码的安全性设计重点1验证码防重复使用验证成功后立即删除Redis中的验证码bladeRedis.del(redisKey)避免同一验证码被多次校验是登录验证码的核心安全要求。重点2验证码时效性控制Redis设置60秒过期setEx既保证用户有足够时间输入又避免验证码长期留存带来的安全风险。重点3格式校验前置手机号、验证码的格式校验如6位数字在业务逻辑最前端执行提前拦截无效请求减少Redis/数据库/短信服务的无效调用。2异常处理与日志记录重点1异常统一封装捕获底层异常如Redis异常、短信发送异常并封装为ServiceException保证前端接收到统一的异常提示避免暴露底层报错重点2关键日志记录验证码生成/校验的关键节点Redis存入、短信发送、校验结果都打印日志便于排查「验证码收不到、校验失败」等问题如日志中记录正确验证码仅用于排查生产需注意脱敏。3接口通用性与扩展性重点1统一响应格式所有接口返回Blade框架的R对象R.data()/R.status()前端可统一解析响应状态和数据重点2枚举管理模板CODETemplateCodeEnum将短信模板CODE抽象为枚举后续新增模板只需扩展枚举无需修改业务代码符合「开闭原则」。2. 核心难点易出问题、需重点关注1短信发送的可靠性问题难点表现短信发送接口可能超时、失败如运营商接口波动、网络问题若未处理会导致「验证码存入Redis但短信未发送」用户收不到验证码但Redis有记录引发体验问题解决方案① 短信发送增加重试机制如重试2次② 短信发送失败时删除Redis中已存入的验证码避免脏数据③ 对接短信服务商的回调接口确认短信是否真正发送成功。2Redis操作的原子性与异常处理难点表现Redis操作setEx/get/del可能抛出异常如Redis集群不可用若未捕获会导致请求报错且可能出现「验证码已生成但未存入Redis」的情况解决方案① 对Redis操作增加try-catch异常时抛出明确的ServiceException如“验证码生成失败请稍后重试”② 生产环境建议使用Redis分布式锁若有并发生成验证码场景避免同一手机号短时间内生成多个验证码。3空值处理与边界场景难点1/phoneCaptcha接口的空值场景若传入的account不存在usernull返回的VO中phone和isVerify为null前端若未处理空值会引发前端报错✅ 优化默认值兜底如phoneAndCaptchaVO.setPhone(StringUtils.defaultIfBlank(user.getPhone(), ));、phoneAndCaptchaVO.setIsVerify(role null ? 0 : role.getIsVerify());。难点2手机号脱敏日志中直接打印完整手机号如log.info(手机号{}, phone)存在数据泄露风险✅ 优化日志中对手机号脱敏如phone.replaceAll((\\d{3})\\d{4}(\\d{4}), $1****$2)。4并发场景下的验证码冲突难点表现同一手机号短时间内多次调用/captcha接口会生成多个验证码覆盖Redis中的旧值导致用户收到多条短信且只能用最后一条验证码解决方案① 增加验证码发送频率限制如同一手机号60秒内只能发送1次Redis存入「发送时间戳」调用/captcha时先校验是否在冷却期② 示例代码StringrateLimitKeyrole_login_captcha_rate_limit:phone;if(bladeRedis.hasKey(rateLimitKey)){thrownewServiceException(验证码发送过于频繁请60秒后重试);}bladeRedis.setEx(rateLimitKey,1,VERIFY_CODE_EXPIRE_SECONDS);5与开篇异常AsyncRequestNotUsableException的关联难点表现若短信发送接口耗时较长如超过前端超时时间前端可能提前断开连接导致控制器返回R对象时触发「流已关闭」异常解决方案① 短信发送改为异步处理Async控制器立即返回“验证码发送中”前端轮询获取发送结果② 调整前端请求超时时间如设置为10秒后端增加响应流状态检测参考上篇回答的异常处理器。三、优化建议补充验证码脱敏日志中隐藏验证码明文如只打印后两位避免数据泄露接口限流对/captcha接口增加IP手机号限流防止恶意刷短信单元测试覆盖针对「验证码过期、格式错误、重复校验、手机号不存在」等边界场景编写测试用例配置化管理将Redis过期时间、短信模板CODE、限流时间等硬编码值抽离到配置文件application.yml便于动态调整。总结该功能的核心思路是「安全、可靠、易用」通过Redis保证验证码的时效性和唯一性通过分层设计降低耦合通过异常处理和日志保证可维护性重难点集中在短信发送可靠性、Redis原子性、边界场景处理、并发控制需重点关注这些环节以避免生产问题。源码/** * 生成验证码接口 */GetMapping(/captcha)ApiOperationSupport(order24)Operation(summary生成验证码接口)publicRStringcaptcha(Stringphone){returnR.data(userService.captcha(phone));}/** * 验证验证码 */PostMapping(/verifyCaptcha)ApiOperationSupport(order25)Operation(summary验证验证码)publicRBooleanverifyCaptcha(Stringphone,Stringcaptcha){returnR.status(userService.verifyCaptcha(phone,captcha));}/** * 登录时返回手机号和是否需要验证码字段给前端 */GetMapping(/phoneCaptcha)ApiOperationSupport(order26)Operation(summary登录时返回手机号和是否需要验证码字段给前端)publicRPhoneAndCaptchaVOphoneCaptcha(Stringaccount){returnR.data(userService.phoneCaptcha(account));}/** * 生成角色登录验证码 * param phone 手机号 * return 包含验证码发送状态的Map接口约定返回格式 */Stringcaptcha(Stringphone);/** * 验证角色登录验证码是否正确 * param phone 手机号 * param captcha 用户输入的验证码 * return 验证结果true成功false失败 */booleanverifyCaptcha(Stringphone,Stringcaptcha);PhoneAndCaptchaVOphoneCaptcha(Stringaccount);/** * 生成验证码 */OverridepublicStringcaptcha(Stringphone){// 1. 手机号格式校验if(!PhoneUtil.isMobile(phone)){thrownewServiceException(手机号格式不正确);}try{// 2. 生成6位随机验证码StringverifyCodeRandomUtil.randomNumbers(6);StringredisKeygetRoleLoginVerifyCodeKey(phone);// 3. 存入Redis60秒过期bladeRedis.setEx(redisKey,verifyCode,VERIFY_CODE_EXPIRE_SECONDS);log.info(角色登录验证码已存入Redis手机号{}验证码{}过期时间60秒,phone,verifyCode);// 4. 发送短信验证码MapString,ObjectsmsParamMap.of(code,verifyCode);smsSending.sendSms(phone,smsParam,TemplateCodeEnum.ROLE_LOGIN_SMS_TEMPLATE.getCode());// 5. 返回字符串结果直接返回提示语return验证码发送成功60秒内有效;}catch(Exceptione){log.error(生成角色登录验证码失败手机号{},phone,e);if(einstanceofServiceException){throw(ServiceException)e;}thrownewServiceException(验证码发送失败请稍后重试);}}/** * 验证验证码 */OverridepublicbooleanverifyCaptcha(Stringphone,Stringcaptcha){// 1. 基础格式校验if(!PhoneUtil.isMobile(phone)){thrownewServiceException(手机号格式不正确);}if(!StringUtils.hasText(captcha)||captcha.length()!6){thrownewServiceException(验证码格式不正确需6位数字);}// 2. 获取Redis中的验证码StringredisKeygetRoleLoginVerifyCodeKey(phone);StringredisCodebladeRedis.get(redisKey);// 3. 校验逻辑if(!StringUtils.hasText(redisCode)){log.warn(角色登录验证码已过期/不存在手机号{},phone);returnfalse;// 验证码过期/未生成}// 4. 验证码匹配booleanisMatchredisCode.equals(captcha);if(isMatch){// 验证成功后删除Redis验证码防止重复使用bladeRedis.del(redisKey);log.info(角色登录验证码校验成功手机号{},phone);}else{log.warn(角色登录验证码校验失败手机号{}输入验证码{}正确验证码{},phone,captcha,redisCode);}returnisMatch;}OverridepublicPhoneAndCaptchaVOphoneCaptcha(Stringaccount){PhoneAndCaptchaVOphoneAndCaptchaVOnewPhoneAndCaptchaVO();Useruserthis.lambdaQuery().eq(User::getAccount,account).one();if(user!null){RoleroleroleService.lambdaQuery().eq(Role::getId,user.getRoleId()).one();if(role!null){phoneAndCaptchaVO.setIsVerify(role.getIsVerify());}phoneAndCaptchaVO.setPhone(user.getPhone());}returnphoneAndCaptchaVO;}/** * 生成角色登录验证码的Redis Key * param phone 手机号 * return 完整的Redis Key */privateStringgetRoleLoginVerifyCodeKey(Stringphone){if(!StringUtils.hasText(phone)){thrownewServiceException(手机号不能为空无法生成Redis Key);}returnROLE_LOGIN_VERIFY_CODE_PREFIXphone;}importio.swagger.v3.oas.annotations.media.Schema;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;DataNoArgsConstructorAllArgsConstructorpublicclassPhoneAndCaptchaVO{Schema(description手机号)privateStringphone;Schema(description是否需要验证码)privateIntegerisVerify;}importlombok.AllArgsConstructor;importlombok.Getter;GetterAllArgsConstructorpublicenumTemplateCodeEnum{/** * 阿里云短信模板枚举 */TO_BE_PROCESSED(1,SMS_123456,收到一条待处理更新提醒用户需要您进入系统处理。),ROLE_LOGIN_SMS_TEMPLATE(2,SMS_123456,登录验证码请勿泄露给其他人);/** * 模板ID */privatefinalIntegerid;/** * 阿里云短信模板CODE */privatefinalStringcode;/** * 模板描述 */privatefinalStringdesc;/** * 根据阿里云模板CODE获取枚举 * * param code 阿里云模板CODE * return 对应的枚举 * throws IllegalArgumentException 如果code不存在 */publicstaticTemplateCodeEnumgetByCode(Stringcode){for(TemplateCodeEnumtemplate:TemplateCodeEnum.values()){if(template.getCode().equals(code)){returntemplate;}}thrownewIllegalArgumentException(未知的阿里云短信模板CODE: code);}}