Spring Boot 集成短信发送功能(以阿里云短信为例)

目录

一. 前置准备工作(创建阿里云OSS服务获取密钥,开通短信服务)

1.1 注册阿里云服务器

1.2 开通短信服务

1.3 创建对象存储OSS服务

1.4 RAM用户授权短信权限

1.5 新增用户并授权用户短信权限

1.6 获取 AccessKey ID 和 AccessKey Secret

二. 项目实战(创建项目编写代码集成短信发送)

2.1 引入依赖

2.2 修改 yml 文件

2.3 创建阿里云短信配置类

2.4 编写 SmsService 短信发送模板业务类

2.5 在业务中调用短信发送的方法


一. 前置准备工作(创建阿里云OSS服务获取密钥,开通短信服务)

这里我们就从申请注册短信模板0基础开始,方便小白同学也能快速上手,如果你已经准备好了短信发送的账户密钥等信息,直接跳至第二部分项目实战。

1.1 注册阿里云服务器

点击链接,直接选择一种登录方式注册登陆即可。

阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台

1.2 开通短信服务

搜索短信服务,点击跳转然后有免费开通。

如下页面,简单过一遍"快速学习与测试"

走完上面的流程,我们点击"国内消息",然后如右侧页面所示,需要去申请"资质",只要是项目集成都需要申请"资质",资质申请审核完毕;到签名页面添加签名,签名会需要使用到申请的资质,签名创建完毕后,添加我们的短信模板,自行选择即可,也可以自定义模板。

创建完毕之后,一定要把"签名","短信模板Code码"记下来,一会在代码中会用到

这里以我的为例,签名是"aliyunSM666"。

短信模板Code为"SMS_481015005"

1.3 创建对象存储OSS服务

左上角搜索输入"对象存储",即可找到对象存储OSS服务,点击跳转。

然后来到如下页面,点击 Bucket——>创建Bucket 一步步操作即可。

这里需要记一个点,如果创建的地域是北京,那么下面在配置 yml 文件时,aliyun.regionId 的值就是 cn-beijing;如果是杭州,就是 cn-hangzhou;如果是上海,就是cn-shanghai。

1.4 RAM用户授权短信权限

创建完毕 Bucket 完毕后,点击创建的 Bucket 进入详情页。

然后前往RAM控制台

1.5 新增用户并授权用户短信权限

来到RAM控制台,先新增一名用户,然后点击添加权限

在权限策略中输入短信即可搜索到,直接授权给用户即可。

如果之前已经创建过用户,直接授权即可,也可以像下图所示,到授权页面选择指定用户授权不能

1.6 获取 AccessKey ID 和 AccessKey Secret

点击右上角用户头像

跳转到 AccessKey 详情页,如果没有则新建一个即可,

注意!!!AssessKey Secret 新建后不会再展示,首次新建请保存,如果遗忘只需禁用现有的AssessKey,再新建一个即可。

二. 项目实战(创建项目编写代码集成短信发送)

2.1 引入依赖

<!-- 阿里云核心 SDK --> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.16</version> </dependency> <!-- 短信服务 SDK --> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-dysmsapi</artifactId> <version>2.1.0</version> </dependency> <!-- OSS SDK --> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.1</version> </dependency>

2.2 修改 yml 文件

如下图所示,修改项目的 yml 文件,在 yml 文件中添加 aliyun 配置。

assessKeyId 就是上面第一步提到的 AccessKey ID;

accessKeySecret 就是上面提到的 AccessKey Secret;

regionId 就是上面提到的 cn-shanghai;根据自己创建的 Bucket 区域而定。

2.3 创建阿里云短信配置类

在下面配置类中,首先获取 yml 配置文件中设置的值,然后编写返回实例的方法并将值赋值给新建实例。

然后,使用短信客户端实例调用 API 接口发送短信,这里写为一个方法,传递手机号,签名,短信模板Code值,然后再其他业务类型,注入此配置类的实例,调用 sendSms 方法,根据业务需求传递不同的短信Code发送不同的短信。

@Configuration public class AliyunSmsConfig { // 1.阿里云账号的accessKeyId @Value("${aliyun.assessKeyId}") private String accessKeyId; // 2.阿里云账号的accessKeySecret @Value("${aliyun.assessKeySecret}") private String accessKeySecret; // 3.阿里云账号的regionId @Value("${aliyun.regionId}") private String regionId; // 创建并初始化阿里云短信服务的客户端实例 @Bean public IAcsClient acsClient() { DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); return new DefaultAcsClient(profile); } // 发送短信的核心逻辑,通过 IAcsClient 调用阿里云短信服务 API public SendSmsResponse sendSms(String phoneNumber, String signName, String templateCode, String templateParam) throws Exception { IAcsClient client = acsClient(); SendSmsRequest request = new SendSmsRequest(); request.setPhoneNumbers(phoneNumber); request.setSignName(signName); request.setTemplateCode(templateCode); request.setTemplateParam(templateParam); return client.getAcsResponse(request); } }

2.4 编写 SmsService 短信发送模板业务类

在上面,我们已经定义好了发送短信的方法,按道理来说,直接 @Autowired 注入 配置的 Bean 实例就可以了,但是我们观察上方方法,其实还不够简洁,因为 sendSms 方法中,只有 phone 手机号一个参数是需要用户传递的,其他的参数是不需要的。

因此,我们最好再进行一层抽取,将每一种发送短信验证码的方法写成一个 Service 业务类,这样一来其他类当要发送短信的时候,直接调用封装好的 Service 中的方法即可,优化了原本的业务类代码。

如下代码所示,

定义一个发送登录验证码的方法 "sendLoginVerificationCode",还可以定义发送修改密码的验证码 "sendUpdatePasswordVerificationCode",还可以定义付钱的验证码方法 "sendPayMoneyVerificationCode"。

当然啦,这里只是举个例子,实际业务项目中,需要发送的短信验证码肯定也是多种多样的,可以定义多个方法,也可以定义一个通用方法,都是可以的,小编这里就采用这种常用写法啦。

@Service @Slf4j public class SmsService { @Autowired private AliyunSmsConfig aliyunSmsConfig; public boolean sendLoginVerificationCode(String phoneNumber, String code) { try { SendSmsResponse response = aliyunSmsConfig.sendSms( phoneNumber, "aliyunSMS666", "SMS_481015005", "{\"code\":\"" + code + "\"}" ); log.info(response.getCode()); return "OK".equals(response.getCode()); } catch (Exception e) { e.printStackTrace(); return false; } } public boolean sendUpdatePasswordVerificationCode(String phoneNumber, String code) { try { SendSmsResponse response = aliyunSmsConfig.sendSms( phoneNumber, "aliyunSMS666", "SMS_481015005", "{\"code\":\"" + code + "\"}" ); log.info(response.getCode()); return "OK".equals(response.getCode()); } catch (Exception e) { e.printStackTrace(); return false; } } public boolean sendPayMoneyVerificationCode(String phoneNumber, String code) { ...... } }

2.5 在业务中调用短信发送的方法

上面的工作都做完之后,我们就可以进入到真正的业务层了,如下代码,其实非常简单,这里小编定义了五个方法,其实这里的 sendCode 方法是可以不用要的,毕竟代码不多,也可以直接将代码写入到 login 登陆方法中。

"selectByUsername" 查询用户方法;

"sendCode"发送短信方法;

"login" 登陆方法;

"createUser" 创建新用户方法;

"generateTokenByUid" 生成用户 token 方法;

逻辑很简单,都有注释,大家一步一步看即可。

@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { // 添加类顶部声明 private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); @Autowired private UserMapper userMapper; @Autowired private SmsService smsService; @Autowired private StringRedisTemplate stringRedisTemplate; // 方法一: 根据用户名查询用户是否已存在 @Override public Boolean selectByUsername(String username) { return !userMapper.selectByUserName(username).isEmpty(); } /* 方法二: 发送短信方法 */ public Boolean sendCode(String phone) { // 1. 生成6位随机数作为验证码 SecureRandom secureRandom = new SecureRandom(); String code = String.valueOf(100000 + secureRandom.nextInt(900000)); // 2. 将验证码存入Redis stringRedisTemplate.opsForValue().set(phone,String.valueOf(code),5, java.util.concurrent.TimeUnit.MINUTES); // 3. 发送验证码 try { logger.info("准备发送验证码,手机号:{},验证码:{}",phone,code); return smsService.sendLoginVerificationCode(phone, code); } catch (Exception e) { logger.error("短信发送失败,手机号:{},错误信息:{}", phone, e.getMessage(), e); return false; } } /* 方法三:用户登录+注册接口综合方法 */ public Result<Boolean> login(LoginDto loginDto, HttpServletRequest request, HttpServletResponse response){ Result<Boolean> result = new Result<>(); // 1. 登陆方式一: 手机号+短信登录 if (loginDto.getPhone() != null && loginDto.getCode() != null){ List<User> users = userMapper.selectUserByPhone(loginDto.getPhone()); if (users.isEmpty()){ createUser(loginDto); } String code = stringRedisTemplate.opsForValue().get(loginDto.getPhone()); if (code != null && code.equals(loginDto.getCode())){ generateTokenByUid(users.get(0).getId(), request, response); result.setCode(200); result.setData(true); result.setMsg("success"); }else{ result.setCode(999); result.setMsg("验证码错误"); result.setData(false); } return result; } // 2. 登陆方式二:手机号+密码登录 else if (loginDto.getUsername() != null && loginDto.getPassword() != null){ User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", loginDto.getUsername())); if (user != null && MD5Utils.md5HashWithSalt(loginDto.getPassword(), user.getSalt()).equals(user.getPassword())){ generateTokenByUid(user.getId(), request, response); result.setCode(200); result.setData(true); result.setMsg("success"); }else{ result.setCode(999); result.setMsg("密码错误"); result.setData(false); } result.setCode(999); result.setMsg("未知错误"); result.setData(false); return result; } // 3. 登陆方式三:用户名+密码登录 else if (loginDto.getUsername() != null || loginDto.getPassword() != null) { User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", loginDto.getUsername())); if (user != null && MD5Utils.md5HashWithSalt(loginDto.getPassword(), user.getSalt()).equals(user.getPassword())){ generateTokenByUid(user.getId(), request, response); result.setCode(200); result.setData(true); result.setMsg("success"); }else if (user == null && loginDto.getPassword() != null && loginDto.getPassword2() != null && loginDto.getPassword().equals(loginDto.getPassword2())){ User newUser = createUser(loginDto); generateTokenByUid(newUser.getId(), request, response); }else { result.setCode(999); result.setMsg("密码错误"); result.setData(false); } } result.setCode(999); result.setMsg("未知错误"); result.setData(false); return result; } /* 方法四:创建新用户方法 */ public User createUser(LoginDto loginDto){ User user = new User(); user.setPhone(loginDto.getPhone()); user.setStatus(0); if (loginDto.getUsername() != null){ user.setUsername(loginDto.getUsername()); // 生成随机密码盐值字符串 String salt = UUID.randomUUID().toString().replaceAll("-", ""); user.setSalt(salt); user.setPassword(MD5Utils.md5HashWithSalt(loginDto.getPassword(), salt)); } user.setAvatar("https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg"); user.setCreated(LocalDateTime.now()); userMapper.insert(user); return user; } /* 方法五:生成 Token 方法*/ public void generateTokenByUid(Long uid, HttpServletRequest request, HttpServletResponse response){ String token = JwtUtils.generateToken(uid); response.setHeader("Authorization", token); response.setHeader("Access-control-Expose-Headers", "Authorization"); } }