阿里云短信申请
开通短信服务
2添加签名管理与模板管理:
阿里云目前已经开放测试用户使用,申请时选择测试用户就可以
获取AccessKey
后端接口开发
1.1首先在maven中能够导入依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
1.2在yml中配置AccessKey和secret
aliyun.sms.regionId=default
aliyun.sms.accessKeyId=自己的
aliyun.sms.secret=自己的
1.3配置redis自定义规则(可以直接用,无需修改)
package com.tan.msm.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
@Configuration
@EnableCaching
public class RedisConfiguration {
/**
* 自定义key规则
* @return
*/
// @Bean(name = "keyGenerator")
// public KeyGenerator keyGenerator() {
// return new KeyGenerator() {
// @Override
// public Object generate(Object target, Method method, Object... params) {
// StringBuilder sb = new StringBuilder();
// sb.append(target.getClass().getName());
// sb.append(method.getName());
// for (Object obj : params) {
// sb.append(obj.toString());
// }
// return sb.toString();
// }
// };
// }
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + Arrays.asList(params).toString();
}
};
}
/**
* 设置RedisTemplate规则
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 设置CacheManager缓存规则
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
1.4编写controller部分:
package com.tan.msm.controller;
import com.tan.msm.service.impl.MSMService;
import com.tan.msm.utils.RandomUtil;
import com.tan.yygh.Result;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("api/msm")
public class MSMController {
@Autowired
private MSMService msmService;
@Resource
private RedisTemplate<String,Object> redisTemplate;
//发送手机验证码,redis设置过期时间
@GetMapping("send/{phone}")
public Result sendCode(@PathVariable("phone") String phone) {
//先从redis取到验证码,获取到就返回ok
//key:手机号 value:验证码
String code = (String)redisTemplate.opsForValue().get(phone);
if (!StringUtils.isEmpty(code)) {
return Result.ok();
}
//如果从redis获取不到,生成验证码,整合短信服务发送
//6位验证码
code = RandomUtil.getFourBitRandom();
//整合阿里云信服务发送
boolean isSend = msmService.send(phone, code);
if (isSend) {
//成功,放到redis,2分钟有效
redisTemplate.opsForValue().set(phone, code, 2, TimeUnit.MINUTES);//key value 有效时间 时间单位
return Result.ok();
} else {
return Result.fail().message("发送短信失败");
}
}
}
1.5Service层(接口类略):
package com.tan.msm.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.tan.msm.service.MSMInterface;
import com.tan.msm.utils.ConstantPropertiesUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/*
阿里云短信业务
* */
@Service
public class MSMService implements MSMInterface {
@Override
public boolean send(String phone, String code) {
//判断手机号是否为空
if(StringUtils.isEmpty(phone)){
return false;
}
//整合阿里云短信服务
//设置相关参数
DefaultProfile profile = DefaultProfile.
getProfile(ConstantPropertiesUtils.REGION_Id,
ConstantPropertiesUtils.ACCESS_KEY_ID,
ConstantPropertiesUtils.SECRECT);
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);//提交方式
request.setDomain("dysmsapi.aliyuncs.com");//域名
request.setVersion("2017-05-25");//版本
request.setAction("SendSms");
//手机号
request.putQueryParameter("PhoneNumbers", phone);
//签名名称
request.putQueryParameter("SignName", "尚医通学习项目");
//模板code
request.putQueryParameter("TemplateCode", "SMS_235790644");
//验证码 使用json格式 {"code":"123456"}
Map<String,Object> param = new HashMap();
param.put("code",code);
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));
//调用方法进行短信发送
try {
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
return response.getHttpResponse().isSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return false;
}
}
service层的代码就是个模板,只需要修改少部分就可以:刚开始肯定要判断前端传递过来的手机号是否为空,不为空就继续进行阿里云短信业务,首先是这一部分DefaultProfile profile = DefaultProfile. getProfile(ConstantPropertiesUtils.REGION_Id, ConstantPropertiesUtils.ACCESS_KEY_ID, ConstantPropertiesUtils.SECRECT);
里边可以写死,你的REGION_Id,ACCESS_KEY_ID,.SECRECT(我是为了方便,弄成了一个工具类),尤其注意以下两个://签名名称 request.putQueryParameter("SignName", "你申请的签名"); //模板code request.putQueryParameter("TemplateCode", "你申请的模板code");
剩下的都是固定套路。
1.6大体流程:
前端请求发过来参数手机号,后端接收,先从redis中根据手机号来判断redis中是否以及存在验证码,如果存在就返回成功,不存在就先随机生成四位验证码,调用service层的短信方法就可以了,另外可以设置redis中的验证码过期时间5分中也可以。
用户短信功能实现接
情景:用户输入手机号后获取到验证码,然后输入验证码,后端进行验证。
1.7封装的参数类:
package com.atguigu.yygh.vo.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description="登录对象")
public class LoginVo {
@ApiModelProperty(value = "openid")
private String openid;
@ApiModelProperty(value = "手机号")
private String phone;
@ApiModelProperty(value = "密码")
private String code;
@ApiModelProperty(value = "IP")
private String ip;
}
这里边的openid和IP是后边微信登录的时候用到
1.8controller层:
@Autowired
UserInfoService userInfoService;
//用户手机号登录
@PostMapping("login")
public Result login(@RequestBody LoginVo loginVo) {
Map<String,Object> map= userInfoService.loginUser(loginVo);
return Result.ok(map);
}
controller层比较简单,就是调用service层方法进行返回一个map集合
1.9 Service层:
ublic class UserInfoServiceImpl extends
ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Override
public Map<String, Object> login(LoginVo loginVo) {
String phone = loginVo.getPhone();
String code = loginVo.getCode();
//校验参数
if(StringUtils.isEmpty(phone) ||
StringUtils.isEmpty(code)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//TODO 校验校验验证码
/校验校验验证码
String mobleCode = redisTemplate.opsForValue().get(phone);
if(!code.equals(mobleCode)) {
throw new YyghException(ResultCodeEnum.CODE_ERROR);
}
//手机号已被使用
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("phone", phone);
//获取会员
UserInfo userInfo = baseMapper.selectOne(queryWrapper);
if(null == userInfo) {
userInfo = new UserInfo();
userInfo.setName("");
userInfo.setPhone(phone);
userInfo.setStatus(1);
this.save(userInfo);
}
//校验是否被禁用
if(userInfo.getStatus() == 0) {
throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
}
//TODO 记录登录
//返回页面显示名称
Map<String, Object> map = new HashMap<>();
String name = userInfo.getName();
if(StringUtils.isEmpty(name)) {
name = userInfo.getNickName();
}
if(StringUtils.isEmpty(name)) {
name = userInfo.getPhone();
}
map.put("name", name);
String token = jwtHelper.createToken(userInfo.getId(), name);
map.put("token",token);
return map
关于JWT:
JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上
JWT最重要的作用就是对 token信息的防伪作用。
jwtHelper工具类代码:
package com.tan.yygh.Helper;
import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;
import java.util.Date;
public class jwtHelper {
//token过期时间:ms单位
private static long tokenExpiration = 24*60*60*1000;
//签名秘钥
private static String tokenSignKey = "123456";
/*
根据字符串生成token
* */
public static String createToken(Long userId, String userName) {
String token = Jwts.builder()
.setSubject("GaoKao-USER")
//设置过期时间,当前时间过后多少ms
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
.claim("userName", userName)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
/*
根据token获取用户id
*/
public static Long getUserId(String token) {
if(StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer)claims.get("userId");
return userId.longValue();
}
//根据token获取用户名
public static String getUserName(String token) {
if(StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws
= Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String)claims.get("userName");
}
public static void main(String[] args) {
String token = jwtHelper.createToken(1L, "55");
System.out.println(token);
System.out.println(jwtHelper.getUserId(token));
System.out.println(jwtHelper.getUserName(token));
}
}
用户信息类:
package com.atguigu.yygh.model.user;
import com.atguigu.yygh.model.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* <p>
* UserInfo
* </p>
*
* @author qy
*/
@Data
@ApiModel(description = "UserInfo")
@TableName("user_info")
public class UserInfo extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "微信openid")
@TableField("openid")
private String openid;
@ApiModelProperty(value = "昵称")
@TableField("nick_name")
private String nickName;
@ApiModelProperty(value = "手机号")
@TableField("phone")
private String phone;
@ApiModelProperty(value = "用户姓名")
@TableField("name")
private String name;
@ApiModelProperty(value = "证件类型")
@TableField("certificates_type")
private String certificatesType;
@ApiModelProperty(value = "证件编号")
@TableField("certificates_no")
private String certificatesNo;
@ApiModelProperty(value = "证件路径")
@TableField("certificates_url")
private String certificatesUrl;
@ApiModelProperty(value = "认证状态(0:未认证 1:认证中 2:认证成功 -1:认证失败)")
@TableField("auth_status")
private Integer authStatus;
@ApiModelProperty(value = "状态(0:锁定 1:正常)")
@TableField("status")
private Integer status;
}
1.10 解读service层:
从前端传递过来的对象中取得phone和code,如果手机号为空就抛出异常,因为短信生成的验证码会存入到redis中,所以把用户输入的和redis中存储的验证码进行比较,如果不同就抛出提示信息,相同则根据手机号判断用户是不是新用户,如果从数据库中取得的数据为空则为新用户,在user_info表中插入用户信息,如果可以从数据库中查出数据说明用户不是新用户,判断用户状态是否是正常状态,不正常的话同样要抛出异常,最后封装自己想要的数据给前端(name,token)
1.11短信测试结果:
短信:
redis中: