网站搭建实操(五)后台管理-短信模块
阿里云短信
用户登陆注册可以使用短信,我这使用阿里云短信
注册阿里云短信
登录阿里云找到短信服务

进入后需要完成五步
- 1.申请资质
- 2.申请签名
- 3.申请模板
- 4.系统设置
- 5.发送短信

以前个人可以申请,但是现在个人需要绑定企业,所以我们申请【短信认证】产品

选择开通

2023年以前的版本不适配,需要升级

可以查看操作指南和api文档

可以看到免费赠送的签名

短信模板

然后绑定手机号去测试

使用登录注册模板

点击发起调用后,手机就会收到验证码

获取AccessKey
鼠标悬停右上角头像,点击 AccessKey

我们需要访问API
自定义名称后点击执行

认证完成后生成AccessKey ID和
AccessKey Secret,这两个在调用api会会用到
短信模块
完整流程图

pom
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>forum-backend</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>forum-sms</artifactId>
<description>微服务论坛系统 - 短信模块</description>
<dependencies>
<!-- 阿里云短信服务SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.6.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Bean Validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>forum-common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
配置文件
java
server:
port: 8084
spring:
application:
name: forum-sms
# 阿里云短信配置
aliyun:
sms:
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID}
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET}
sign-name: 论坛APP
template-code: SMS_123456789
region-id: cn-hangzhou
domain: dysmsapi.aliyuncs.com
version: 2017-05-25
# 验证码有效期(秒)
code-expire-seconds: 300
# 发送频率限制(秒)
send-rate-limit-seconds: 60
accessKey和secret可以直接写在配置中,也可以配置在服务器环境变量中
在服务器上设置环境变量(推荐)
bash
export ALIYUN_SMS_ACCESS_KEY_ID=LTAI5txxxxxxxxxxxx
export ALIYUN_SMS_ACCESS_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
读取配置
java
package com.forum.sms.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 阿里云短信配置类
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "aliyun.sms")
public class SmsConfig {
private String accessKeyId;
private String accessKeySecret;
private String signName;
private String templateCode;
private String regionId = "cn-hangzhou";
private String domain = "dysmsapi.aliyuncs.com";
private String version = "2017-05-25";
private Integer codeExpireSeconds = 300;
private Integer sendRateLimitSeconds = 60;
}
工具类
java
package com.forum.sms.config.utils;
import com.forum.common.exception.BusinessException;
import com.forum.common.utils.RedisUtils;
import com.forum.sms.config.SmsConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
/**
* 阿里云短信工具类
*
* 功能:封装阿里云短信SDK调用
* 流程:生成验证码 → 存入Redis → 调用阿里云API发送短信
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SmsUtils {
private final SmsConfig smsConfig;
private final RedisUtils redisUtils;
private static final String ACTION_SEND_SMS = "SendSms";
private static final String REDIS_CODE_PREFIX = "sms:code:";
private static final String REDIS_RATE_LIMIT_PREFIX = "sms:rate:";
/**
* 发送短信验证码
*
* @param phone 手机号
* @return 验证码
*/
public String sendSmsCode(String phone) {
// 1. 频率限制检查
checkRateLimit(phone);
// 2. 生成6位随机验证码
String code = generateCode();
// 3. 调用阿里云发送短信
boolean success = sendSms(phone, code);
if (success) {
// 4. 将验证码存入Redis,设置过期时间
String redisKey = REDIS_CODE_PREFIX + phone;
redisUtils.set(redisKey, code, smsConfig.getCodeExpireSeconds(), java.util.concurrent.TimeUnit.SECONDS);
// 5. 记录发送频率限制
String rateLimitKey = REDIS_RATE_LIMIT_PREFIX + phone;
redisUtils.set(rateLimitKey, "1", smsConfig.getSendRateLimitSeconds(), java.util.concurrent.TimeUnit.SECONDS);
log.info("短信验证码发送成功: phone={}, code={}", phone, code);
return code;
}
throw new BusinessException("短信发送失败,请稍后重试");
}
/**
* 校验验证码
*/
public boolean verifyCode(String phone, String code) {
String redisKey = REDIS_CODE_PREFIX + phone;
String cachedCode = (String) redisUtils.get(redisKey);
if (cachedCode == null) {
throw new BusinessException("验证码已过期,请重新获取");
}
if (!cachedCode.equals(code)) {
throw new BusinessException("验证码错误");
}
// 验证成功后删除验证码,防止重复使用
redisUtils.delete(redisKey);
return true;
}
/**
* 调用阿里云API发送短信
*/
private boolean sendSms(String phone, String code) {
try {
// 创建客户端
DefaultProfile profile = DefaultProfile.getProfile(
smsConfig.getRegionId(),
smsConfig.getAccessKeyId(),
smsConfig.getAccessKeySecret()
);
IAcsClient client = new DefaultAcsClient(profile);
// 构建请求
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain(smsConfig.getDomain());
request.setSysVersion(smsConfig.getVersion());
request.setSysAction(ACTION_SEND_SMS);
// 设置参数
request.putQueryParameter("RegionId", smsConfig.getRegionId());
request.putQueryParameter("PhoneNumbers", phone);
request.putQueryParameter("SignName", smsConfig.getSignName());
request.putQueryParameter("TemplateCode", smsConfig.getTemplateCode());
request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");
// 发送请求
CommonResponse response = client.getCommonResponse(request);
String data = response.getData();
log.debug("阿里云短信响应: {}", data);
// 解析响应
return data != null && data.contains("\"Code\":\"OK\"");
} catch (ClientException e) {
log.error("阿里云短信发送失败: {}", e.getMessage(), e);
return false;
}
}
/**
* 频率限制检查
*/
private void checkRateLimit(String phone) {
String rateLimitKey = REDIS_RATE_LIMIT_PREFIX + phone;
Object rateLimit = redisUtils.get(rateLimitKey);
if (rateLimit != null) {
throw new BusinessException("发送过于频繁,请" + smsConfig.getSendRateLimitSeconds() + "秒后再试");
}
}
/**
* 生成6位随机验证码
*/
private String generateCode() {
int code = (int) ((Math.random() * 9 + 1) * 100000);
return String.valueOf(code);
}
}
dto
java
@Data
public class SmsSendDTO {
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
private String type = "login"; // login:登录注册, reset:找回密码
}
package com.forum.sms.config.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@Data
public class SmsVerifyDTO {
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@NotBlank(message = "验证码不能为空")
@Pattern(regexp = "^\\d{6}$", message = "验证码必须是6位数字")
private String code;
}
controller
java
package com.forum.sms.config.controller;
import com.forum.common.result.Result;
import com.forum.sms.config.dto.SmsSendDTO;
import com.forum.sms.config.dto.SmsVerifyDTO;
import com.forum.sms.config.service.SmsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import static com.forum.common.result.Result.success;
@Slf4j
@Api(tags = "短信服务")
@RestController
@RequestMapping("/api/sms")
@RequiredArgsConstructor
public class SmsController {
private final SmsService smsService;
@ApiOperation("发送验证码")
@PostMapping("/send")
public Result<Void> sendCode(@Valid @RequestBody SmsSendDTO dto) {
smsService.sendCode(dto);
return success();
}
@ApiOperation("校验验证码")
@PostMapping("/verify")
public Result<Void> verifyCode(@Valid @RequestBody SmsVerifyDTO dto) {
smsService.verifyCode(dto);
return success();
}
}
实现类
java
package com.forum.sms.config.service.impl;
import com.forum.sms.config.dto.SmsSendDTO;
import com.forum.sms.config.dto.SmsVerifyDTO;
import com.forum.sms.config.service.SmsService;
import com.forum.sms.config.utils.SmsUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class SmsServiceImpl implements SmsService {
private final SmsUtils smsUtils;
@Override
public void sendCode(SmsSendDTO dto) {
log.info("发送短信验证码: phone={}, type={}", dto.getPhone(), dto.getType());
smsUtils.sendSmsCode(dto.getPhone());
}
@Override
public void verifyCode(SmsVerifyDTO dto) {
log.info("校验短信验证码: phone={}", dto.getPhone());
smsUtils.verifyCode(dto.getPhone(), dto.getCode());
}
}