文章目录
5、注册登录需求分析
5.1、业务说明
5.2、需求分析
5.3、数据库表
5.4、实体类
6、短信验证码
6.1、阿里云短信服务
6.2、模板组件
5、注册登录需求分析
5.1、业务说明
用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。
- 已注册用户:
- 输入手机号发送验证码
- 输入验证码,进行比对完成登录
- 未注册用户:
- 输入手机号发送验证码
- 输入验证码,进行比对,自动注册(保存用户)
- 完善用户信息
5.2、需求分析
服务端接受客户端请求
Java代码调用第三方服务实现短信返送(发送短信需要运营资质,只能借助第三方实现)
5.3、数据库表
数据库使用的mysql:
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
`password` varchar(32) DEFAULT NULL COMMENT '密码,需要加密',
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `mobile` (`mobile`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';
CREATE TABLE `tb_user_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
`logo` varchar(100) DEFAULT NULL COMMENT '用户头像',
`tags` varchar(50) DEFAULT NULL COMMENT '用户标签:多个用逗号分隔',
`sex` int(1) DEFAULT '3' COMMENT '性别,1-男,2-女,3-未知',
`age` int(11) DEFAULT NULL COMMENT '用户年龄',
`edu` varchar(20) DEFAULT NULL COMMENT '学历',
`city` varchar(20) DEFAULT NULL COMMENT '居住城市',
`birthday` varchar(20) DEFAULT NULL COMMENT '生日',
`cover_pic` varchar(50) DEFAULT NULL COMMENT '封面图片',
`industry` varchar(20) DEFAULT NULL COMMENT '行业',
`income` varchar(20) DEFAULT NULL COMMENT '收入',
`marriage` varchar(20) DEFAULT NULL COMMENT '婚姻状态',
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
5.4、实体类
User
@Data
@AllArgsConstructor //满参构造方法
@NoArgsConstructor //无参构造方法
public class User extends BasePojo {
private Long id;
private String mobile;
private String password;
//环信用户信息
private String hxUser;
private String hxPassword;
}
6、短信验证码
6.1、阿里云短信服务
官方网站:短信服务_企业短信营销推广_验证码通知-阿里云
6.1.1、申请签名与模板
使用阿里云短信服务非常简单,仅需要简单的申请和认证即可
阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
说明:申请签名时,个人用户只能申请一个并且签名的名称必须为“ABC商城”,否则审核不通过。
申请模板:
审核时间需要1~2小时,请耐心等待~
6.1.2、示例代码
文档:SendSms - 发送短信 - 短信服务 - 阿里云:
导入依赖:
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.1</version>
</dependency>
package com.tanhua.sso.service;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
public class SendSms {
/**
* 使用AK&SK初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @return Client
* @throws Exception
*/
public static com.aliyun.dysmsapi20170525.Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
Config config = new Config()
// 您的AccessKey ID
.setAccessKeyId(accessKeyId)
// 您的AccessKey Secret
.setAccessKeySecret(accessKeySecret)
.setEndpoint("dysmsapi.aliyuncs.com");
// 访问的域名
return new com.aliyun.dysmsapi20170525.Client(config);
}
public static void main(String[] args_) throws Exception {
java.util.List<String> args = java.util.Arrays.asList(args_);
com.aliyun.dysmsapi20170525.Client client = SendSms
.createClient("**********", "**********");
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers( "158****7944") //目标手机号
.setSignName("ABC商城") //签名名称
.setTemplateCode("SMS_204756062") //短信模板code
.setTemplateParam("{\"code\":\"1111\"}"); //模板中变量替换
SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = sendSmsResponse.getBody();
// code = OK 代表成功
System.out.println(body.getCode() + " " + body.getMessage());
}
}
3.6.1.4、实现发送短信方法
配置文件:aliyun.properties
aliyun.sms.accessKeyId = ***********
aliyun.sms.accessKeySecret = ***********
aliyun.sms.domain= dysmsapi.aliyuncs.com
aliyun.sms.signName= ABC商城
aliyun.sms.templateCode= SMS_204756062
需要注意中文编码问题:
读取配置:
package com.tanhua.sso.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:aliyun.properties")
@ConfigurationProperties(prefix = "aliyun.sms")
@Data
public class AliyunSMSConfig {
private String accessKeyId;
private String accessKeySecret;
private String domain;
private String signName;
private String templateCode;
}
代码实现:
package com.tanhua.sso.service;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.tanhua.sso.config.AliyunSMSConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class SmsService {
@Autowired
private AliyunSMSConfig aliyunSMSConfig;
/**
* 发送短信验证码
*
* @param mobile
* @return
*/
public String sendSms(String mobile) {
//随机生成6位数字验证码
String code = RandomUtil.randomNumbers(6);
try {
Config config = new Config()
.setAccessKeyId(this.aliyunSMSConfig.getAccessKeyId())
.setAccessKeySecret(this.aliyunSMSConfig.getAccessKeySecret())
.setEndpoint(this.aliyunSMSConfig.getDomain());
Client client = new Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers(mobile)//目标手机号
.setSignName(this.aliyunSMSConfig.getSignName()) //签名名称
.setTemplateCode(this.aliyunSMSConfig.getTemplateCode()) //短信模板code
.setTemplateParam("{\"code\":\"" + code + "\"}"); //模板中变量替换
SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = sendSmsResponse.getBody();
if (StrUtil.equals("OK", body.getCode())) {
return code;
}
} catch (Exception e) {
log.error("发送短信验证码失败!" + mobile, e);
}
return null;
}
}
6.2、模板组件
企业开发中,往往将常见工具类封装抽取,以简洁便利的方式供其他工程模块使用。而SpringBoot的自动装配机制可以方便的实现组件抽取。SpringBoot执行流程如下
- 扫描依赖模块中META-INF/spring.factories
- 执行装配类中方法
- 对象存入容器中
- 核心工程注入对象,调用方法使用
6.2.1、配置类
tanhua-autoconfig创建配置信息类
@Data
@ConfigurationProperties(prefix = "tanhua.sms")
public class SmsProperties {
private String signName;
private String templateCode;
private String accessKey;
private String secret;
}
6.2.2、发送短信模板对象
tanhua-autoconfig创建模板对象对象发送信息
package com.tanhua.autoconfig.template;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.tanhua.autoconfig.properties.SmsProperties;
public class SmsTemplate {
private SmsProperties properties;
public SmsTemplate(SmsProperties properties) {
this.properties = properties;
}
public void sendSms(String mobile,String code) {
try {
//配置阿里云
Config config = new Config()
// 您的AccessKey ID
.setAccessKeyId(properties.getAccessKey())
// 您的AccessKey Secret
.setAccessKeySecret(properties.getSecret());
// 访问的域名
config.endpoint = "dysmsapi.aliyuncs.com";
com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers(mobile)
.setSignName(properties.getSignName())
.setTemplateCode(properties.getTemplateCode())
.setTemplateParam("{\"code\":\""+code+"\"}");
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse response = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = response.getBody();
System.out.println(body.getMessage());
}catch (Exception e) {
e.printStackTrace();
}
}
}
6.2.3、自动装配类
tanhua-autoconfig创建自动装配的配置类
package com.tanhua.autoconfig;
import com.tanhua.autoconfig.properties.*;
import com.tanhua.autoconfig.template.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@EnableConfigurationProperties({
SmsProperties.class
})
public class TanhuaAutoConfiguration {
@Bean
public SmsTemplate smsTemplate(SmsProperties properties) {
return new SmsTemplate(properties);
}
}
6.2.4、自动装配配置
根据自动装配原则,在tanhua-autoconfig工程创建 /META-INF/spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.tanhua.autoconfig.TanhuaAutoConfiguration
6.2.5、测试
tanhua-app-server工程加入短信配置
tanhua: sms: signName: 物流云商 templateCode: SMS_106590012 accessKey: LTAI4GKgob9vZ53k2SZdyAC7 secret: LHLBvXmILRoyw0niRSBuXBZewQ30la
编写单元测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class SmsTemplateTest {
//注入
@Autowired
private SmsTemplate smsTemplate;
//测试
@Test
public void testSendSms() {
smsTemplate.sendSms("18618412321","4567");
}
}
3.6.2、SSO短信接口服务
3.6.2.1、mock接口
地址:YApi-高效、易用、功能强大的可视化接口管理平台
3.6.2.2、编写接口服务
编写ErrorResult,ErrorResult对象是与前端约定好的结构,如果发生错误需要返回该对象,如果未发生错误响应200即可。
package com.tanhua.sso.vo;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ErrorResult {
private String errCode;
private String errMessage;
}
SmsController:
package com.tanhua.sso.controller;
import com.tanhua.sso.service.SmsService;
import com.tanhua.sso.vo.ErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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 java.util.Map;
@RestController
@RequestMapping("user")
@Slf4j
public class SmsController {
@Autowired
private SmsService smsService;
/**
* 发送短信验证码接口
*
* @param param
* @return
*/
@PostMapping("login")
public ResponseEntity<ErrorResult> sendCheckCode(@RequestBody Map<String, String> param) {
ErrorResult errorResult = null;
String phone = param.get("phone");
try {
errorResult = this.smsService.sendCheckCode(phone);
if (null == errorResult) {
return ResponseEntity.ok(null);
}
} catch (Exception e) {
log.error("发送短信验证码失败~ phone = " + phone, e);
errorResult = ErrorResult.builder().errCode("000002").errMessage("短信验证码发送失败!").build();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);
}
}
SmsService:
package com.tanhua.sso.service;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.tanhua.sso.config.AliyunSMSConfig;
import com.tanhua.sso.vo.ErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
@Service
@Slf4j
public class SmsService {
@Autowired
private AliyunSMSConfig aliyunSMSConfig;
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 发送短信验证码
*
* @param mobile
* @return
*/
public String sendSms(String mobile) {
//随机生成6位数字验证码
String code = RandomUtil.randomNumbers(6);
try {
Config config = new Config()
.setAccessKeyId(this.aliyunSMSConfig.getAccessKeyId())
.setAccessKeySecret(this.aliyunSMSConfig.getAccessKeySecret())
.setEndpoint(this.aliyunSMSConfig.getDomain());
Client client = new Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers(mobile)//目标手机号
.setSignName(this.aliyunSMSConfig.getSignName()) //签名名称
.setTemplateCode(this.aliyunSMSConfig.getTemplateCode()) //短信模板code
.setTemplateParam("{\"code\":\"" + code + "\"}"); //模板中变量替换
SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = sendSmsResponse.getBody();
if (StrUtil.equals("OK", body.getCode())) {
return code;
}
} catch (Exception e) {
log.error("发送短信验证码失败!" + mobile, e);
}
return null;
}
/**
* 发送短信验证码
* 实现:发送完成短信验证码后,需要将验证码保存到redis中
* @param phone
* @return
*/
public ErrorResult sendCheckCode(String phone) {
String redisKey = "CHECK_CODE_" + phone;
//先判断该手机号发送的验证码是否还未失效
if(this.redisTemplate.hasKey(redisKey)){
String msg = "上一次发送的验证码还未失效!";
return ErrorResult.builder().errCode("000001").errMessage(msg).build();
}
String code = this.sendSms(phone);
if(StrUtil.isEmpty(code)){
String msg = "发送短信验证码失败!";
return ErrorResult.builder().errCode("000000").errMessage(msg).build();
}
//短信发送成功,将验证码保存到redis中,有效期为5分钟
this.redisTemplate.opsForValue().set(redisKey, code, Duration.ofMinutes(5));
return null;
}
}