@@ -41,7 +41,7 @@ import org.dromara.sms4j.api.SmsBlend;
 
		
	
		
			
				import   org.dromara.sms4j.api.entity.SmsResponse ;  
		
	
		
			
				import   org.dromara.sms4j.comm.constant.SupplierConstant ;  
		
	
		
			
				import   org.dromara.sms4j.core.factory.SmsFactory ;  
		
	
		
			
				import   org.redisson.api.RateType  ;  
		
	
		
			
				import   org.redisson.api.RateIntervalUnit  ;  
		
	
		
			
				import   org.springframework.http.HttpHeaders ;  
		
	
		
			
				import   org.springframework.validation.annotation.Validated ;  
		
	
		
			
				import   org.springframework.web.bind.annotation.* ;  
		
	
	
		
			
				
					
					
						
					 
				
			
			@@ -57,6 +57,9 @@ import top.continew.starter.core.util.validate.CheckUtils;
 
		
	
		
			
				import   top.continew.starter.core.util.validate.ValidationUtils ;  
		
	
		
			
				import   top.continew.starter.log.core.annotation.Log ;  
		
	
		
			
				import   top.continew.starter.messaging.mail.util.MailUtils ;  
		
	
		
			
				import   top.continew.starter.security.limiter.annotation.RateLimiter ;  
		
	
		
			
				import   top.continew.starter.security.limiter.annotation.RateLimiters ;  
		
	
		
			
				import   top.continew.starter.security.limiter.enums.LimitType ;  
		
	
		
			
				import   top.continew.starter.web.model.R ;  
		
	
		
			
				 
		
	
		
			
				import   java.time.Duration ;  
		
	
	
		
			
				
					
					
						
					 
				
			
			@@ -116,14 +119,28 @@ public class CaptchaController {
 
		
	
		
			
				         return   R . ok ( CaptchaResp . builder ( ) . uuid ( uuid ) . img ( captcha . toBase64 ( ) ) . expireTime ( expireTime ) . build ( ) ) ;  
		
	
		
			
				     }  
		
	
		
			
				 
		
	
		
			
				     /**  
		
	
		
			
				     * 获取邮箱验证码  
		
	
		
			
				     *  
		
	
		
			
				     * <p>  
		
	
		
			
				     * 限流规则:<br>  
		
	
		
			
				     * 1.同一邮箱同一模板, , ,   
		
	
		
			
				     * 2、同一邮箱所有模板 24 小时 100 条 <br>  
		
	
		
			
				     * 3、同一 IP 每分钟限制发送 30 条  
		
	
		
			
				     * </p>  
		
	
		
			
				     *   
		
	
		
			
				     * @param email 邮箱  
		
	
		
			
				     * @return /  
		
	
		
			
				     */  
		
	
		
			
				     @Operation ( summary   =   " 获取邮箱验证码 " ,   description   =   " 发送验证码到指定邮箱 " )  
		
	
		
			
				     @GetMapping ( " /mail " )  
		
	
		
			
				     @RateLimiters ( {  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   " MIN " ,   key   =   " #email + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.mail.templatePath') " ,   rate   =   2 ,   interval   =   1 ,   unit   =   RateIntervalUnit . MINUTES ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   " HOUR " ,   key   =   " #email + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.mail.templatePath') " ,   rate   =   8 ,   interval   =   1 ,   unit   =   RateIntervalUnit . HOURS ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   " DAY' " ,   key   =   " #email + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.mail.templatePath') " ,   rate   =   20 ,   interval   =   24 ,   unit   =   RateIntervalUnit . HOURS ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX ,   key   =   " #email " ,   rate   =   100 ,   interval   =   24 ,   unit   =   RateIntervalUnit . HOURS ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX ,   key   =   " #email " ,   rate   =   30 ,   interval   =   1 ,   unit   =   RateIntervalUnit . MINUTES ,   type   =   LimitType . IP ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) } )  
		
	
		
			
				     public   R < Void >   getMailCaptcha ( @NotBlank ( message   =   " 邮箱不能为空 " )   @Pattern ( regexp   =   RegexPool . EMAIL ,   message   =   " 邮箱格式错误 " )   String   email )   throws   MessagingException   {  
		
	
		
			
				         String   limitKeyPrefix   =   CacheConstants . LIMIT_KEY_PREFIX ;  
		
	
		
			
				         String   captchaKeyPrefix   =   CacheConstants . CAPTCHA_KEY_PREFIX ;  
		
	
		
			
				         String   limitCaptchaKey   =   limitKeyPrefix   +   captchaKeyPrefix   +   email ;  
		
	
		
			
				         long   limitTimeInMillisecond   =   RedisUtils . getTimeToLive ( limitCaptchaKey ) ;  
		
	
		
			
				         CheckUtils . throwIf ( limitTimeInMillisecond   >   0 ,   " 发送验证码过于频繁,请您 {}s 后再试 " ,   limitTimeInMillisecond   /   1000 ) ;  
		
	
		
			
				         // 生成验证码  
		
	
		
			
				         CaptchaProperties . CaptchaMail   captchaMail   =   captchaProperties . getMail ( ) ;  
		
	
		
			
				         String   captcha   =   RandomUtil . randomNumbers ( captchaMail . getLength ( ) ) ;  
		
	
	
		
			
				
					
					
						
					 
				
			
			@@ -138,42 +155,40 @@ public class CaptchaController {
 
		
	
		
			
				             . set ( " expiration " ,   expirationInMinutes ) ) ;  
		
	
		
			
				         MailUtils . sendHtml ( email ,   " 【%s】邮箱验证码 " . formatted ( projectProperties . getName ( ) ) ,   content ) ;  
		
	
		
			
				         // 保存验证码  
		
	
		
			
				         String   captchaKey   =   captchaKeyPrefix   +   email ;  
		
	
		
			
				         String   captchaKey   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   email ;  
		
	
		
			
				         RedisUtils . set ( captchaKey ,   captcha ,   Duration . ofMinutes ( expirationInMinutes ) ) ;  
		
	
		
			
				         RedisUtils . set ( limitCaptchaKey ,   captcha ,   Duration . ofSeconds ( captchaMail . getLimitInSeconds ( ) ) ) ;  
		
	
		
			
				         return   R . ok ( " 发送成功,验证码有效期 %s 分钟 " . formatted ( expirationInMinutes ) ) ;  
		
	
		
			
				     }  
		
	
		
			
				 
		
	
		
			
				     /**  
		
	
		
			
				     * 获取短信验证码  
		
	
		
			
				     *  
		
	
		
			
				     * <p>  
		
	
		
			
				     * 限流规则:<br>  
		
	
		
			
				     * 1.同一号码同一模板, , ,   
		
	
		
			
				     * 2、同一号码所有模板 24 小时 100 条 <br>  
		
	
		
			
				     * 3、同一 IP 每分钟限制发送 30 条  
		
	
		
			
				     * </p>  
		
	
		
			
				     *   
		
	
		
			
				     * @param phone      手机号  
		
	
		
			
				     * @param captchaReq 行为验证码信息  
		
	
		
			
				     * @return /  
		
	
		
			
				     */  
		
	
		
			
				     @Operation ( summary   =   " 获取短信验证码 " ,   description   =   " 发送验证码到指定手机号 " )  
		
	
		
			
				     @GetMapping ( " /sms " )  
		
	
		
			
				     @RateLimiters ( {  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   " MIN " ,   key   =   " #phone + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.sms.templateId') " ,   rate   =   2 ,   interval   =   1 ,   unit   =   RateIntervalUnit . MINUTES ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   " HOUR " ,   key   =   " #phone + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.sms.templateId') " ,   rate   =   8 ,   interval   =   1 ,   unit   =   RateIntervalUnit . HOURS ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   " DAY' " ,   key   =   " #phone + ':' + T(cn.hutool.extra.spring.SpringUtil).getProperty('captcha.sms.templateId') " ,   rate   =   20 ,   interval   =   24 ,   unit   =   RateIntervalUnit . HOURS ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX ,   key   =   " #phone " ,   rate   =   100 ,   interval   =   24 ,   unit   =   RateIntervalUnit . HOURS ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) ,  
		
	
		
			
				         @RateLimiter ( name   =   CacheConstants . CAPTCHA_KEY_PREFIX ,   key   =   " #phone " ,   rate   =   30 ,   interval   =   1 ,   unit   =   RateIntervalUnit . MINUTES ,   type   =   LimitType . IP ,   message   =   " 获取验证码操作太频繁,请稍后再试 " ) } )  
		
	
		
			
				     public   R < Void >   getSmsCaptcha ( @NotBlank ( message   =   " 手机号不能为空 " )   @Pattern ( regexp   =   RegexPool . MOBILE ,   message   =   " 手机号格式错误 " )   String   phone ,  
		
	
		
			
				                                  CaptchaVO   captchaReq ,  
		
	
		
			
				                                  HttpServletRequest   request )   {  
		
	
		
			
				                                  CaptchaVO   captchaReq )   {  
		
	
		
			
				         // 行为验证码校验  
		
	
		
			
				         ResponseModel   verificationRes   =   behaviorCaptchaService . verification ( captchaReq ) ;  
		
	
		
			
				         ValidationUtils . throwIfNotEqual ( verificationRes . getRepCode ( ) ,   RepCodeEnum . SUCCESS . getCode ( ) ,   verificationRes  
		
	
		
			
				             . getRepMsg ( ) ) ;  
		
	
		
			
				         CaptchaProperties . CaptchaSms   captchaSms   =   captchaProperties . getSms ( ) ;  
		
	
		
			
				         String   templateId   =   captchaSms . getTemplateId ( ) ;  
		
	
		
			
				         String   limitKeyPrefix   =   CacheConstants . LIMIT_KEY_PREFIX ;  
		
	
		
			
				         String   captchaKeyPrefix   =   CacheConstants . CAPTCHA_KEY_PREFIX ;  
		
	
		
			
				         String   limitTemplateKeyPrefix   =   limitKeyPrefix   +   captchaKeyPrefix ;  
		
	
		
			
				         // 限制短信发送频率  
		
	
		
			
				         // 1.同一号码同一短信模板, , , ,   
		
	
		
			
				         final   String   errorMsg   =   " 获取验证码操作太频繁,请稍后再试 " ;  
		
	
		
			
				         CheckUtils . throwIf ( ! RedisUtils . rateLimit ( RedisUtils  
		
	
		
			
				             . formatKey ( limitTemplateKeyPrefix   +   " MIN " ,   phone ,   templateId ) ,   RateType . OVERALL ,   2 ,   60 ) ,   errorMsg ) ;  
		
	
		
			
				         CheckUtils . throwIf ( ! RedisUtils . rateLimit ( RedisUtils  
		
	
		
			
				             . formatKey ( limitTemplateKeyPrefix   +   " HOUR " ,   phone ,   templateId ) ,   RateType . OVERALL ,   8 ,   60   *   60 ) ,   errorMsg ) ;  
		
	
		
			
				         CheckUtils . throwIf ( ! RedisUtils . rateLimit ( RedisUtils  
		
	
		
			
				             . formatKey ( limitTemplateKeyPrefix   +   " DAY " ,   phone ,   templateId ) ,   RateType . OVERALL ,   20 ,   60   *   60   *   24 ) ,   errorMsg ) ;  
		
	
		
			
				         // 2.同一号码所有短信模板 24 小时 100 条,   
		
	
		
			
				         String   limitPhoneKey   =   limitKeyPrefix   +   captchaKeyPrefix   +   phone ;  
		
	
		
			
				         CheckUtils . throwIf ( ! RedisUtils . rateLimit ( limitPhoneKey ,   RateType . OVERALL ,   100 ,   60   *   60   *   24 ) ,   errorMsg ) ;  
		
	
		
			
				         // 3.同一 IP 每分钟限制发送 30 条,   
		
	
		
			
				         String   limitIpKey   =   RedisUtils . formatKey ( limitKeyPrefix   +   captchaKeyPrefix   +   " PHONE " ,   JakartaServletUtil  
		
	
		
			
				             . getClientIP ( request ) ) ;  
		
	
		
			
				         CheckUtils . throwIf ( ! RedisUtils . rateLimit ( limitIpKey ,   RateType . OVERALL ,   30 ,   60 ) ,   errorMsg ) ;  
		
	
		
			
				         // 生成验证码  
		
	
		
			
				         String   captcha   =   RandomUtil . randomNumbers ( captchaSms . getLength ( ) ) ;  
		
	
		
			
				         // 发送验证码  
		
	
	
		
			
				
					
					
						
					 
				
			
			@@ -186,7 +201,7 @@ public class CaptchaController {
 
		
	
		
			
				             . getTemplateId ( ) ,   ( LinkedHashMap < String ,   String > ) messageMap ) ;  
		
	
		
			
				         CheckUtils . throwIf ( ! smsResponse . isSuccess ( ) ,   " 验证码发送失败 " ) ;  
		
	
		
			
				         // 保存验证码  
		
	
		
			
				         String   captchaKey   =   captchaKeyPrefix   +   phone ;  
		
	
		
			
				         String   captchaKey   =   CacheConstants . CAPTCHA_KEY_PREFIX   +   phone ;  
		
	
		
			
				         RedisUtils . set ( captchaKey ,   captcha ,   Duration . ofMinutes ( expirationInMinutes ) ) ;  
		
	
		
			
				         return   R . ok ( " 发送成功,验证码有效期 %s 分钟 " . formatted ( expirationInMinutes ) ) ;  
		
	
		
			
				     }