Java通过阿里云短信实现短信验证示例

阿里云短信使用很简单

首先我们先引入jar包

		<!-- 阿里云短信 -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.3</version>
        </dependency>

然后我们再加一个工具类用来发送验证码

package com.sakura.user.tool;

import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class AliyunSmsUtils {

    @Value("${aliyun.sms.access-key-id}")
    String ALIYUN_SMS_ACCESS_KEY_ID;

    @Value("${aliyun.sms.secret}")
    String ALIYUN_SMS_SECRET;

    @Value("${aliyun.sms.region-id}")
    String ALIYUN_SMS_REGION_ID;

    @Value("${aliyun.sms.sys-domain}")
    String ALIYUN_SMS_SYS_DOMAIN;

    @Value("${aliyun.sms.sys-action}")
    String ALIYUN_SMS_SYS_ACTION;

    @Value("${aliyun.sms.sys-version}")
    String ALIYUN_SMS_SYS_VERSION;

    public boolean sendSms(String phone, String paramJson, String signName, String templateCode) {
        DefaultProfile profile = DefaultProfile.getProfile(ALIYUN_SMS_REGION_ID, ALIYUN_SMS_ACCESS_KEY_ID, ALIYUN_SMS_SECRET);
        IAcsClient client = new DefaultAcsClient(profile);

        CommonRequest request = new CommonRequest();
        request.setSysMethod(MethodType.POST);
        request.setSysDomain(ALIYUN_SMS_SYS_DOMAIN);
        request.setSysVersion(ALIYUN_SMS_SYS_VERSION);
        request.setSysAction(ALIYUN_SMS_SYS_ACTION);
        request.putQueryParameter("RegionId", ALIYUN_SMS_REGION_ID);
        request.putQueryParameter("PhoneNumbers", phone);
        request.putQueryParameter("SignName", signName);
        request.putQueryParameter("TemplateCode", templateCode);
        request.putQueryParameter("TemplateParam", paramJson);

        try {
            CommonResponse response = client.getCommonResponse(request);
			JSONObject json = JSONObject.parseObject(response.getData());
            if ("OK".equals(json.get("Message"))) {
                return true;
            } else {
				log.error("阿里云短信发送失败:" + phone + "+" + paramJson);
            }
        } catch (Exception e) {
            log.error("阿里云短信发送异常:" + phone + "+" + paramJson);
            e.printStackTrace();
        }
        return false;
    }
}

这里有一些配置参数,这些在阿里云平台都可以拿得到

aliyun:
  sms: # 阿里云短信服务参数,申请开通的时候有
    access-key-id: LTde4GJggqwQ4hvzrP8feyAf # 这里是假的大家就不要用这个了
    secret: ivseKA8iHAtsVderlmL31s54fe78QA # 这里是假的大家就不要用这个了
    region-id: cn-hangzhou
    sys-domain: dysmsapi.aliyuncs.com
    sys-action: SendSms
    sys-version: 2017-05-25
    send-max-num: 5 # 每天每个手机号发送短信最大次数限制,防止恶意脚本
    register: # 用户认证信息模版
      sign-name: Sakura-Cloud  # 这里是假的大家就不要用这个了
      template-code: SMS_202320123  # 这里是假的大家就不要用这个了
      template-param: code # 阿里云短信模板参数名  # 这里是假的大家就不要用这个了

然后发送短信验证码接口一定要做好防护,比如加上图片验证码和每天发送次数限制,我们之前就出现过恶意脚本调用我们的短信认证接口疯狂发送短信

下面是短信验证码请求示例
首先是controller方法

package com.sakura.user.controller;

import com.sakura.common.api.ApiResult;
import com.sakura.common.enums.OperationLogType;
import com.sakura.common.log.Module;
import com.sakura.common.log.OperationLog;
import com.sakura.user.param.SMSCodeParam;
import com.sakura.user.service.CaptchaService;
import com.sakura.user.vo.PictureCodeVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * @author Sakura
 * @date 2023/8/14 14:15
 */
@Slf4j
@RestController
@RequestMapping("/captcha")
@Module("user")
@Api(value = "验证码管理", tags = {"验证码管理"})
public class CaptchaController {

    @Autowired
    private CaptchaService codeService;

    /**
     * 图片验证码
     */
    @GetMapping("getPictureCode")
    @OperationLog(name = "获取图片验证码", type = OperationLogType.INFO)
    @ApiOperation(value = "获取图片验证码")
    public ApiResult<PictureCodeVo> getPictureCode() throws Exception {
        PictureCodeVo pictureCodeVo = codeService.getPictureCode();
        return ApiResult.ok(pictureCodeVo);
    }

    /**
     * 短信验证码
     */
    @PostMapping(value = "/getSMSCode")
    @OperationLog(name = "获取短信验证码", type = OperationLogType.INFO)
    @ApiOperation(value = "获取短信验证码")
    public ApiResult<String> getSMSCode(@Validated @RequestBody SMSCodeParam smsCodeParam) throws Exception {
        String msg = codeService.getSMSCode(smsCodeParam);
        return ApiResult.ok(msg);
    }
}

然后是service方法

package com.sakura.user.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.sakura.common.constant.CommonConstant;
import com.sakura.common.exception.BusinessException;
import com.sakura.common.redis.RedisUtil;
import com.sakura.common.tool.DateUtil;
import com.sakura.common.tool.StringUtil;
import com.sakura.user.captcha.GifCaptcha;
import com.sakura.user.param.SMSCodeParam;
import com.sakura.user.service.CaptchaService;
import com.sakura.user.tool.AliyunSmsUtils;
import com.sakura.user.vo.PictureCodeVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * @author Sakura
 * @date 2023/8/14 14:25
 */
@Service
@Slf4j
public class CaptchaServiceImpl implements CaptchaService {

    @Value("${aliyun.sms.send-max-num}")
    Integer ALIYUN_SMS_SEND_MAX_NUM;

    @Value("${aliyun.sms.register.sign-name}")
    String ALIYUN_SMS_REGISTER_SIGN_NAME;

    @Value("${aliyun.sms.register.template-code}")
    String ALIYUN_SMS_REGISTER_TEMPLATE_CODE;

    @Value("${aliyun.sms.register.template-param}")
    String ALIYUN_SMS_REGISTER_TEMPLATE_PARAM;

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private AliyunSmsUtils aliyunSmsUtils;

    @Override
    public PictureCodeVo getPictureCode() throws Exception {
        // 通过GifCaptcha生成图片验证码
        GifCaptcha gifCaptcha = new GifCaptcha(130, 48, 5);
        String verCode = gifCaptcha.text().toLowerCase();

        PictureCodeVo pictureCodeVo = new PictureCodeVo();
        String key = UUID.randomUUID().toString();
        pictureCodeVo.setKey(key);
        pictureCodeVo.setImage(gifCaptcha.toBase64());

        log.info(key + "+++++++++++++" + verCode); // 测试用的,建议删除

        // 将验证码放入Redis,有效期5分钟
        redisUtil.set(key, verCode, 60 * 5);

        return pictureCodeVo;
    }

    @Override
    public String getSMSCode(SMSCodeParam smsCodeParam) throws Exception {
        // 校验图片验证码是否正确,防止脚本刷短信
        if (!redisUtil.hasKey(smsCodeParam.getKey())) {
            throw new BusinessException(500, "图片验证码已过期");
        }
        String verCode = redisUtil.get(smsCodeParam.getKey()).toString();
        if (StringUtil.isEmpty(verCode) || !verCode.equals(smsCodeParam.getPictureCode())) {
            throw new BusinessException(500, "图片验证码已过期");
        }
        // 验证码使用后就删除
        redisUtil.del(smsCodeParam.getKey());

        // 用户每天发送短信不得超过最大限制数
        long smsNum = redisUtil.incr("sms-send-num" + smsCodeParam.getMobile(), 1);
        if (smsNum > ALIYUN_SMS_SEND_MAX_NUM) {
            throw new BusinessException(500, "当天短信发送数量已达最大");
        }
        // 设置当前短信记录发送有效期,当前日期到晚上23:59:59
        redisUtil.expire(CommonConstant.SMS_SEND_NUM + smsCodeParam.getMobile(), DateUtil.timeToMidnight());

        // 发送短信验证码
        String smsCode = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 生成一个6位数验证码
        log.info(smsCode); // 测试用,建议删除
        JSONObject jsonParam = new JSONObject();
        jsonParam.put(ALIYUN_SMS_REGISTER_TEMPLATE_PARAM, smsCode);
        boolean sendStatus = aliyunSmsUtils.sendSms(smsCodeParam.getMobile(), jsonParam.toJSONString(),
                ALIYUN_SMS_REGISTER_SIGN_NAME, ALIYUN_SMS_REGISTER_TEMPLATE_CODE);
        if (!sendStatus) {
            redisUtil.decr(CommonConstant.SMS_SEND_NUM + smsCodeParam.getMobile(), 1); // 短信发送失败不计算次数
            throw new BusinessException(500, "短信发送失败,请联系管理员");
        }
        // 将短信验证码放入Redis,有效期5分钟
        redisUtil.set(CommonConstant.SMS_CODE + smsCodeParam.getMobile(), smsCode, 60 * 5);

        return "验证码已发送至手机号:" + smsCodeParam.getMobile() + ",5分钟内有效!";
    }
}

图片验证码的返回参数PictureCodeVo

package com.sakura.user.vo;

import com.sakura.common.base.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * @author Sakura
 * @date 2023/8/14 14:19
 */
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "图片验证码")
public class PictureCodeVo extends BaseEntity {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty("key")
    private String key;

    @ApiModelProperty("图片验证码 Base64格式")
    private String image;
}

还有验证码请求参数SMSCodeParam

package com.sakura.user.param;

import com.sakura.common.base.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

/**
 * 用户表
 *
 * @author Sakura
 * @since 2023-08-14
 */
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "短信验证码参数")
public class SMSCodeParam extends BaseEntity {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty("手机号")
    @Pattern(regexp = "^$|^((13[0-9])|(15[^4])|(18[0-9])|(17[0-8])|(16[0-8])|(147))\\d{8}$", message = "手机号码格式错误")
    private String mobile;

    @ApiModelProperty("key")
    @NotBlank(message = "key不能为空")
    private String key;

    @ApiModelProperty("图片验证码")
    @NotBlank(message = "图片验证码不能为空")
    private String pictureCode;


}

比如我在用户注册的时候需要用到这个验证码

	@Override
    public boolean register(UserRegisterParam userRegisterParam) throws Exception {
        // 先校验短信验证码是否正确
        if (!redisUtil.hasKey(CommonConstant.SMS_CODE + userRegisterParam.getMobile())) {
            throw new BusinessException(500, "短信验证码已过期");
        }
        String smsCode = redisUtil.get(CommonConstant.SMS_CODE + userRegisterParam.getMobile()).toString();
        if (StringUtil.isEmpty(smsCode) || !smsCode.equals(userRegisterParam.getSmsCode())) {
            throw new BusinessException(500, "短信验证码错误");
        }
        // 验证码使用后就删除
        redisUtil.del(CommonConstant.SMS_CODE + userRegisterParam.getMobile());

        // 校验当前手机号是否已存在
        User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().
                eq(User::getMobile, userRegisterParam.getMobile()).
                eq(User::getStatus, 1));
        if (user != null) {
            throw new BusinessException(500, "用户已存在");
        }

        // 添加用户信息
        user = new User();
        BeanUtils.copyProperties(userRegisterParam, user);
        user.setUserId(StringUtil.random(32)); // 生成用户ID
        user.setSalt(SHA256Util.getSHA256Str(UUID.randomUUID().toString())); // 随机生成盐
        // 如果用户未设置密码那么初始密码为123456
        if (StringUtil.isEmpty(userRegisterParam.getPassword())) {
            // 原始密码为123456
            // 前端传输时先加密SHA256(123456)
            userRegisterParam.setPassword(SHA256Util.getSHA256Str("123456"));
        }
        // 二次加密
        user.setPassword(SHA256Util.getSHA256Str(userRegisterParam.getPassword() + user.getSalt()));
        user.setStatus(1);

        userMapper.insert(user);

        return true;
    }

最后生成动态图片验证码的方法在我之前一篇博客有写:Java-生成动态图片验证码

其它的就是一些Redis和String的工具类,大家要是需要建议直接拉我代码:https://github.com/PX1206/Sakura-Cloud
当然有任何不懂的地方也可以直接问我

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子非衣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值