目录
1.6 获取 AccessKey ID 和 AccessKey Secret
一. 创建阿里云OSS服务并获取密钥,开通短信服务
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 激素hi上面提到的 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");
}
}