第一步。前端调用发送短信验证码的接口
@Autowired
private ResetPasswordService resetPasswordService;
@GetMapping("/validate-code")
public Result validateCode(@RequestParam String phone) {
return resetPasswordService.validateCode(phone);
}
第二步,生成验证码并发送(自主生成验证码),反馈给前端
@Autowired
private IIdentifyingCodeService identifyingCodeService;
public static final String USER_REGISTER_PASSWORD = "register_password";
//要发送的短信内容
private String templateOfRegisterStoreSms = "【***-**】%s(****验证码)。工作人员不会向您索要,请勿向任何人泄露,以免造成账户或资金损失。";
//验证码的生成范围
private int startInclusive = 1000;
private int endInclusive = 9999;
//验证码有效期
private int expire = 60 * 1000;
@Override
public String validateCode(String phone) {
//生成验证码
int code = RandomUtils.nextInt(startInclusive, endInclusive);
String tokenId= identifyingCodeService.sendCode(phone, USER_REGISTER_PASSWORD, templateOfRegisterStoreSms,String.valueOf(code), expire);
return tokenId;
}
第三步,发送验证码的service和impl
/**
* identifying code service
* @author author
* @date 2020年2月14日
*/
public interface IIdentifyingCodeService {
/**
* 发送认证码
* @param target 目标,一般为手机号
* @param type 类型,该类型由具体的使用者自定义即可,只要确保在本方法使用中的type一致即可,该字段作为tokenId的一部分
* @param template 消息模板,的定义,默认使用 %s 来格式化字符串,即模板中包含的 %s 将被 参数 code 替换
* @param code 认证码,请自主生成
* @param expire 超时时间,该认证码失效的时间,单位为秒
* @return tokenId 认证码与本tokenId一一对应
*/
String sendCode(String target, String type, String template, String code, int expire);
/**
* 获取tokenId对应的code
* @param type 参考上述定义
* @param tokenId
* @return code
*/
String getCode(String type, String tokenId);
/**
* 清空tokenId对应的code
* @param type 参考上述定义
* @param tokenId }方法返回的tokenId
* @return code
*/
void clean(String type, String tokenId);
}
/**
* identifying code service impl
* @author author
* @date 2020年2月14日
*/
@Service
@Slf4j
public class IIdentifyingCodeServiceImpl implements IIdentifyingCodeService {
public static final String PREFIX_SMS = "SMS";
public static final String PREFIX_IDENTIFYING = "IDENTIFYING";
public static final String IDENTIFYING_TYPE_OF_POLICY = "policy";
private String separator = ":";
private int keyLength = 10;
private int policyTime = 60 * 1000;
@Autowired
private RedisUtils redisUtils;
@Override
public String sendCode(String target, String type, String template, String code, int expire) {
Result result = timePolicy(target, policyTime);
if(result.getCode().equals(ResultEnum.OPRATOR_FAIL.getCode())){
return result;
}
//阿里发送短信验证码
AliSMSClient.sendSMS(target,code);
String tokenId = generatorTokenId();
redisUtils.setValue(generatorKey(type, tokenId), code, expire);
return tokenId;
}
protected String generatorKey(String type, String tokenId) {
String key = new StringBuffer()
.append(PREFIX_SMS)
.append(separator)
.append(PREFIX_IDENTIFYING)
.append(separator)
.append(type)
.append(separator)
.append(tokenId).toString();
return key;
}
protected String generatorTokenId() {
return RandomStringUtils.randomAlphabetic(keyLength);
}
protected Result timePolicy(String target, int ttl) {
Object oTarget= redisUtils.getValue(generatorKey(IDENTIFYING_TYPE_OF_POLICY, target));
if (Objects.nonNull(oTarget)) {
log.debug("identifying code time policy: target [{}] is failed. ttl=[{}]", target, ttl);
return RespUtil.error(ResultEnum.OPRATOR_FAIL.getCode(),ttl / 1000 + "秒内不能再次发送短信");
} else {
redisUtils.setValue(generatorKey(IDENTIFYING_TYPE_OF_POLICY, target), target, ttl);
return RespUtil.success(ResultEnum.OPRATOR_SUCCESS.getCode());
}
}
@Override
public String getCode(String type, String tokenId) {
Object obj = redisUtils.getValue(generatorKey(type, tokenId));
if(Objects.isNull(obj)){
return "";
}
return (String)obj;
}
@Override
public void clean(String type, String tokenId) {
redisUtils.setValue(generatorKey(type, tokenId), null);
}
}
第四步,ali发送短信的接口
/**
* 静态常量类
* @author author
*
*/
public class Constants {
/** 阿里 短信发送访问域名 */
public static final String endpoint = "dysmsapi.aliyuncs.com";
/** 阿里 AccessKey ID */
public static final String accessKeyId = "accessKeyId ";
/** 阿里 AccessKey Secret */
public static final String accessKeySecret = "accessKeySecret ";
/** 阿里 signName */
public static final String signName = "signName";
/** 阿里 templateCode */
public static final String templateCode = "templateCode ";
}
/**
*
* @author author
*
*/
public class AliSMSClient {
private static Logger logger = LoggerFactory.getLogger(AliSMSClient.class);
private AliSMSClient(){
}
public static void main(String[] args) {
sendSMS("18700000000", "3456");
}
/**
* 使用AK&SK初始化账号Client
* @return Client
* @throws Exception
*/
public static Client createClient() throws Exception {
Config config = new Config()
// 您的AccessKey ID
.setAccessKeyId(Constants.accessKeyId)
// 您的AccessKey Secret
.setAccessKeySecret(Constants.accessKeySecret)
// 访问的域名
.setEndpoint(Constants.endpoint);
return new Client(config);
}
/**
* 发送短信
* @param phone
* @param SignName 短信签名名称
* @param TemplateCode 短信模板ID
* @return
* @throws Exception
*/
public static String sendSMS(String phone,String templateParam) {
if(StringUtils.isBlank(phone)) {
throw new IllegalArgumentException("手机号不能为空");
}
Client client = null;
String ret = "";
try {
client = createClient();
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers(phone)
.setSignName(Constants.signName)
.setTemplateCode(Constants.templateCode)
.setTemplateParam("{'code':'" + templateParam + "'}");
SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
if (null != sendSmsResponse && !"".equals(sendSmsResponse)) {
ret = parseResponse(sendSmsResponse);
}
} catch (Exception e) {
e.printStackTrace();
throw new CustomException("发送短信失败!");
}
return ret;
}
/**
* 解析下发response
* @param sendSmsResponse
* @return
*/
public static String parseResponse(SendSmsResponse sendSmsResponse) {
//"Message":"OK", 状态码的描述。
//"RequestId":"2184201F-BFB3-446B-B1F2-C746B7BF0657", 请求ID。
//"BizId":"197703245997295588^0", 发送回执ID,可根据该ID在接口QuerySendDetails中查询具体的发送状态。
//"Code":"OK" 请求状态码,返回OK代表请求成功。
SendSmsResponseBody body = sendSmsResponse.getBody();
if("OK".equals(body.getCode())){
return "0";
}else {
return "-1";
}
}
}
下面是验证码校验
@PostMapping("/reset/password")
public Result resetPassword(@RequestBody ResetPasswordDTO dto) {
return resetPasswordService.resetPassword(dto);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result resetPassword(ResetPasswordDTO dto) {
Optional<String> oToken = Optional.ofNullable(identifyingCodeService.getCode(USER_REGISTER_PASSWORD,
dto.getTokenId()));
if (oToken.isPresent()) {
if (oToken.get().equals(dto.getCode())) {
//验证成功,进行操作
//清除验证码
identifyingCodeService.clean(USER_REGISTER_PASSWORD, dto.getTokenId());
return RespUtil.success();
} else {
return RespUtil.error(ResultEnum.OPRATOR_FAIL.getCode(),"短信验证码错误.");
}
} else {
return RespUtil.error(ResultEnum.OPRATOR_FAIL.getCode(),"短信验证码错误.");
}
}
/**
* @author author
* @since 2020-09-01
*/
@Data
public class ResetPasswordDTO implements Serializable {
/**
* 验证码
*/
private String code;
/**
* 验证码id
*/
private String tokenId;
}