签名申请
在开始之前,需要事前申请签名和创建短信模板,后面代码中会用到,地址在这里 腾讯云->申请签名,申请完等待审核通过即可。
- 申请签名
- 创建模板
- 等待审核
代码实现
pom.xml
首先要安装下腾讯云的包
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.62</version>
<scope>compile</scope>
</dependency>
application.yml
把代码中需要用到的一些重要信息配置到yml中。
sms:
# 腾讯云中的secretId
secretId: AKIDb3MYaQ2QjvwmOnyukOq24vc7EWSCVcXE
# 腾讯云中的secretKey
secretKey: VpzyjIjy0oNjv0ywthXCiDXcVEkJewer
# 腾讯云中的appid,在应用列表那里
appid: 1400795322
# 签名管理中的前面内容
signName: 若依页面分层工具网
# 使用的端点,腾讯云默认sms.tencentcloudapi.com
endpoint: sms.tencentcloudapi.com
# 地区
region: ap-shanghai
sms配置类
通过@ConfigurationProperties
读取配置信息并与 bean 绑定,在后面的业务代码中会读取这些配置信息。
@Data
@Component
@ConfigurationProperties(prefix = "sms")
public class MessageConfig {
/** 腾讯云中的secretId */
private String secretId;
/** 腾讯云中的secretKey */
private String secretKey;
/** 腾讯云中的appid */
private String appid;
/** 签名 */
private String signName;
/** 使用的端点 */
private String endpoint;
/** 地区 */
private String region;
}
sms短信实现
使用redis
缓存每次发送的验证码,过期时间为10mins;表单提交时,做验证码的有效性和正确性判断。
下面这些代码是获取验证码的主要部分,后面在实现类中直接调该方法即可!
@Service
public class Message {
@Autowired
private MessageConfig messageConfig;
@Autowired
private RedisCache redisCache;
public SendSmsResponse smsClient(String phone) throws TencentCloudSDKException {
/* 必要步骤:
* 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
* 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
* 以免泄露密钥对危及你的财产安全。
* CAM密匙查询: https://console.cloud.tencent.com/cam/capi*/
Credential cred = new Credential(messageConfig.getSecretId(), messageConfig.getSecretKey());
// 实例化一个http选项,可选,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
/* SDK默认使用POST方法。
* 如果你一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */
httpProfile.setReqMethod("POST");
/* SDK有默认的超时时间,非必要请不要进行调整
* 如有需要请在代码中查阅以获取最新的默认值 */
httpProfile.setConnTimeout(60);
/* SDK会自动指定域名。通常是不需要特地指定域名的,但是如果你访问的是金融区的服务
* 则必须手动指定域名,例如sms的上海金融区域名: sms.ap-shanghai-fsi.tencentcloudapi.com */
httpProfile.setEndpoint(messageConfig.getEndpoint());
/* 非必要步骤:
* 实例化一个客户端配置对象,可以指定超时时间等配置 */
ClientProfile clientProfile = new ClientProfile();
/* SDK 默认用 TC3-HMAC-SHA256 进行签名
* 非必要请不要修改该字段 */
clientProfile.setSignMethod("HmacSHA256");
clientProfile.setHttpProfile(httpProfile);
SmsClient client = new SmsClient(cred, messageConfig.getRegion(), clientProfile);
/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
* 你可以直接查询SDK源码确定接口有哪些属性可以设置
* 属性可能是基本类型,也可能引用了另一个数据结构
* 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */
SendSmsRequest req = new SendSmsRequest();
/* 填充请求参数,这里request对象的成员变量即对应接口的入参
* 你可以通过官网接口文档或跳转到request对象的定义处查看请求参数的定义
* 基本类型的设置:
* 帮助链接:
* 短信控制台: https://console.cloud.tencent.com/smsv2
* sms helper: https://cloud.tencent.com/document/product/382/3773 */
/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */
String sdkAppId = messageConfig.getAppid();
req.setSmsSdkAppid(sdkAppId);
/* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看 */
String signName = messageConfig.getSignName();
req.setSign(signName);
/* 国际/港澳台短信 SenderId: 国内短信填空,默认未开通,如需开通请联系 [sms helper] */
String senderid = "";
req.setSenderId(senderid);
/* 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
String sessionContext = SecurityUtils.getUsername();
req.setSessionContext(sessionContext);
/* 短信号码扩展号: 默认未开通,如需开通请联系 [sms helper] */
String extendCode = "";
req.setExtendCode(extendCode);
/* 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看 */
String templateId = "1771557";
req.setTemplateID(templateId);
/* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
* 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 */
String[] phoneNumberSet = {"+86" + phone};
req.setPhoneNumberSet(phoneNumberSet);
/* 模板参数: 若无模板参数,则设置为空 */
int code = (int) ((Math.random() * 9 + 1) * 1000); //生成6位随机数字
String[] templateParamSet = {String.valueOf(code)};
req.setTemplateParamSet(templateParamSet);
// redis缓存手机号对应的验证码,这里的code要存入字符串类型,要和前端传入的code类型保持一致!
redisCache.setCacheObject(Constants.SMS_CODE_KEY + phone, String.valueOf(code), Constants.SMS_EXPIRATION, TimeUnit.MINUTES);
/* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
* 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */
SendSmsResponse res = client.SendSms(req);
return res;
}
}
Constants类
public class Constants{
/**
* sms有效期
*/
public static final Integer SMS_EXPIRATION = 10;
/**
* 短信sms redis key
*/
public static final String SMS_CODE_KEY = "sms_codes:";
}
redis配置
添加依赖
<!-- redis 缓存操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
修改配置
# Spring配置
spring:
redis:
# 地址
host: xxx.xx.xx.xx
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: root
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
序列化和反序列化redis的key值
序列化
- 狭义的层面:将对象转换为字节
- 广义的层面:将对象转换为指定格式的字符串
反序列化
- 狭义的层面:将字节转换为对象
- 广义的层面:将指定格式的字符串转换为对象
序列化和反序列化代码
/**
* redis配置
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public DefaultRedisScript<Long> limitScript()
{
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 限流脚本
*/
private String limitScriptText()
{
return "local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local time = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key);\n" +
"if current and tonumber(current) > count then\n" +
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', key)\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', key, time)\n" +
"end\n" +
"return tonumber(current);";
}
}
}
增加工具类
redis工具类
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param mapkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
}
接口
Controller
get
请求,传递一个手机号码即可。
@RestController
@RequestMapping("/msg")
public class SmsMessage {
@Autowired
private ISmsService ISmsService;
//发送短信
@GetMapping("/sendMsg")
public SendSmsResponse sendMessage(String phone) throws TencentCloudSDKException {
return ISmsService.sendLoginVeryCodeMessage(phone);
}
}
Service
public interface ISmsService {
public SendSmsResponse sendLoginVeryCodeMessage(String phone) throws TencentCloudSDKException;
}
impl
只需要在实现类中调用封装好的sms业务代码即可!
@Service
public class SmsServiceImpl implements ISmsService {
@Autowired
private Message message;
public SendSmsResponse sendLoginVeryCodeMessage(String phone) throws TencentCloudSDKException {
return message.smsClient(phone);
}
}
验证码校验
此时此刻,已经通过前端页面请求获取到验证码,并且填写至表单中,提交的时候需要对当前的提交的验证码进行校验。之前发送验证码的时候,已经把验证码的内容保存到了redis中;此时只需要把redis缓存中的数据取出来,提交信息中的电话号码为redis的key,根据这个key获取对应的验证码,比对和提交的验证码是否一致即可!
@PostMapping
public AjaxResult add(@RequestBody MerApply merApply) {
// 获取提交的手机号码
String smsCode = Constants.SMS_CODE_KEY + merApply.getPhone();
// 根据手机号码获取redis缓存中的value
String code = redisCache.getCacheObject(smsCode);
if (code == null) {
return error("验证码已失效");
}
if (!merApply.getCode().equals(code)) {
return error("验证码校验不通过");
}
merApply.setCreateBy(getUsername());
return toAjax(this.merApplyService.insert(merApply));
}