分析
- 黑线:短信发送验证码的实现
- 红线:用户填写用户信息以及验证验证码是否正确完成注册
基本步骤(下面有详细实现:
- 前端controller
- 前端service
- 使用到web-user(war)、service-user(war)、interface、common、dao(由逆向工程生成)以及springboot项目共六个module
- web-user、service-user、common分别引入配置文件
- 首先在interface项目中src/main/java下编写接口
- 再在service-user项目中src/main/java下编写实现类
- 最后在web-user项目中src/main/java下编写controller
- 编写消息服务器项目(spring boot)
- 测试,启动顺序:zookeeper→Redis→启动类→service-user→web-user
- 查看数据库
PS:
- 下面提供的前端controller、service为了方便理清前后端调用关系,明确后台路径和参数书写,登录页面就不提供了,按钮都是点击事件
- 在启动service-user之前要先启动zookeeper,因为是分布式项目;启动Redis,验证码要用到Redis
1. 前端controller
//控制层
app.controller('userController' ,function($scope,$controller ,userService){
//注册用户
$scope.reg=function(){
//比较两次输入的密码是否一致
if($scope.password!=$scope.entity.password){
alert("两次输入密码不一致,请重新输入");
$scope.entity.password="";
$scope.password="";
return ;
}
//新增
userService.add($scope.entity,$scope.smscode).success(
function(response){
alert(response.message);
}
);
}
//发送验证码
$scope.sendCode=function(){
if($scope.entity.phone==null || $scope.entity.phone==""){
alert("请填写手机号码");
return ;
}
userService.sendCode($scope.entity.phone ).success(
function(response){
alert(response.message);
}
);
}
});
2. 前端service
//服务层
app.service('userService',function($http){
//增加
this.add=function(entity,smscode){
return $http.post('../user/add.do?smscode='+smscode,entity );
}
//发送验证码
this.sendCode=function(phone){
return $http.get('../user/sendCode.do?phone='+phone);
}
});
3. 会使用到web-user(war)、service-user(war)、interface、common、dao(由逆向工程生成)以及springboot项目共六个module
4. 在web-user、service-user、common子项目中分别引入配置文件
- web-user项目--->src/main/resources下创建spring目录并引入springmvc.xml配置文件;再配置web.xml
springmvc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json"/>
<property name="features">
<array>
<value>WriteMapNullValue</value>
<value>WriteDateUseDateFormat</value>
</array>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 引用dubbo 服务 -->
<dubbo:application name="pinyougou-user-web" />
<dubbo:registry address="zookeeper://192.168.200.128:2181"/>
<!--扫描包-->
<dubbo:annotation package="cn.zf.core.controller" />
<!-- 超时全局设置 10分钟 check=false不检查服务提供方-->
<dubbo:consumer timeout="600000" check="false"/>
</beans>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- 解决post乱码 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>home-index.html</welcome-file>
</welcome-file-list>
</web-app>
- service-user项目--->src/main/resources下创建spring目录并引入applicationContext-jms-producer.xml、applicationContext-service.xml、applicationContext-tx.xml配置文件;再配置web.xml
applicationContext-jms-producer.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.200.128:61616"/>
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
</bean>
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="connectionFactory"/>
</bean>
<!--这个是队列目的地,点对点的 文本信息-->
<bean id="smsDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="sms"/>
</bean>
</beans>
applicationContext-service.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<dubbo:protocol name="dubbo" port="20886"></dubbo:protocol>
<dubbo:application name="pinyougou-user-service"/>
<dubbo:registry address="zookeeper://192.168.200.128:2181"/>
<!--扫描包-->
<dubbo:annotation package="cn.zf.service" />
</beans>
applicationContext-tx.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启事务控制的注解支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
- common项目--->src/main/java下创建util包作为工具包并引入一个工具类PhoneFormatCheckUtils,作后续使用;再在src/main/resources下创建properties目录引入dubbox.properties、redis-config.properties、sms.properties配置文件,再继续创建一个spring目录引入applicationContext-dubbox.xml、applicationContext-redis.xml
PhoneFormatCheckUtils:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class PhoneFormatCheckUtils {
/**
* 大陆号码或香港号码均可
*/
public static boolean isPhoneLegal(String str)throws PatternSyntaxException {
return isChinaPhoneLegal(str) || isHKPhoneLegal(str);
}
/**
* 大陆手机号码11位数,匹配格式:前三位固定格式+后8位任意数
* 此方法中前三位格式有:
* 13+任意数
* 15+除4的任意数
* 18+除1和4的任意数
* 17+除9的任意数
* 147
*/
public static boolean isChinaPhoneLegal(String str) throws PatternSyntaxException {
String regExp = "^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147))\\d{8}$";
Pattern p = Pattern.compile(regExp);
Matcher m = p.matcher(str);
return m.matches();
}
/**
* 香港手机号码8位数,5|6|8|9开头+7位任意数
*/
public static boolean isHKPhoneLegal(String str)throws PatternSyntaxException {
String regExp = "^(5|6|8|9)\\d{7}$";
Pattern p = Pattern.compile(regExp);
Matcher m = p.matcher(str);
return m.matches();
}
}
dubbox.properties:
address=192.168.200.128:2181
redis-config.properties:
# Redis settings
# server IP
redis.host=192.168.200.128
# server port
redis.port=6379
# server pass
redis.pass=
# use dbIndex
redis.database=0
# \u63A7\u5236\u4E00\u4E2Apool\u6700\u591A\u6709\u591A\u5C11\u4E2A\u72B6\u6001\u4E3Aidle(\u7A7A\u95F2\u7684)\u7684jedis\u5B9E\u4F8B
redis.maxIdle=300
# \u8868\u793A\u5F53borrow(\u5F15\u5165)\u4E00\u4E2Ajedis\u5B9E\u4F8B\u65F6\uFF0C\u6700\u5927\u7684\u7B49\u5F85\u65F6\u95F4\uFF0C\u5982\u679C\u8D85\u8FC7\u7B49\u5F85\u65F6\u95F4(\u6BEB\u79D2)\uFF0C\u5219\u76F4\u63A5\u629B\u51FAJedisConnectionException\uFF1B
redis.maxWait=3000
# \u5728borrow\u4E00\u4E2Ajedis\u5B9E\u4F8B\u65F6\uFF0C\u662F\u5426\u63D0\u524D\u8FDB\u884Cvalidate\u64CD\u4F5C\uFF1B\u5982\u679C\u4E3Atrue\uFF0C\u5219\u5F97\u5230\u7684jedis\u5B9E\u4F8B\u5747\u662F\u53EF\u7528\u7684
redis.testOnBorrow=true
sms.properties:
#template_code是模板CODE
template_code=SMS_175542263
#sign_name是短信签名 要转为Unicode
sign_name=\u54c1\u4f18\u8d2d
applicationContext-dubbox.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath*:properties/*.properties" />
<dubbo:registry protocol="zookeeper" address="${address}"/>
</beans>
applicationContext-redis.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory" />
</bean>
</beans>
5. 首先在interface项目中src/main/java下编写接口
public interface UserService {
//获取验证码
void sendCode(String phone);
//判断验证码是否正确
Boolean checkSmsCode(String phone,String smsCode);
//注册用户
void add(User user);
}
6. 再在service-user项目中src/main/java下编写实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private ActiveMQQueue smsDestination;
@Autowired
//逆向工程生成
private UserDao userDao;
@Value("${template_code}")
private String template_code;
@Value("${sign_name}")
private String sign_name;
@Override
public void sendCode(final String phone) {
//1.生成随机数
StringBuffer stringBuffer = new StringBuffer();
for (int i = 1; i < 7; i++) {
int num = new Random().nextInt(10);
stringBuffer.append(num);
}
System.out.println(stringBuffer);
//2.将手机号码作为key 验证码作为value 存到Redis中 生存时间10分钟
redisTemplate.boundValueOps(phone).set(stringBuffer.toString(),60*10, TimeUnit.SECONDS);
final String SMS_CODE = stringBuffer.toString();
//3.将手机号码、短信内容、模板编号、短信签名封装到map中 再发送给消息服务器
jmsTemplate.send(smsDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
MapMessage message = session.createMapMessage();
message.setString("mobile",phone);
message.setString("template_code",template_code);
message.setString("sign_name",sign_name);
Map map = new HashMap();
map.put("code",SMS_CODE);
message.setString("param", JSON.toJSONString(map));
return message;
}
});
}
@Override
public Boolean checkSmsCode(String phone, String smsCode) {
if (phone==null||smsCode==null||"".equals(phone)||"".equals(smsCode)){
return false;
}
//1.根据手机号码到Redis中获取自己的验证码
String redisSmsCode = (String) redisTemplate.boundValueOps(phone).get();
//2.判断页面传入的验证码和Redis中的验证码是否一致
if (smsCode.equals(redisSmsCode)){
return true;
}
return false;
}
@Override
public void add(User user) {
//insertSelective逆向工程中的方法
userDao.insertSelective(user);
}
}
7. 最后在web-user项目中src/main/java下编写controller
//@RestController转json
@RestController
@RequestMapping("/user")
public class UserController {
//import com.alibaba.dubbo.config.annotation.Reference;
@Reference
private UserService userService;
//获取短信验证码
@RequestMapping("/sendCode")
public Result sendCode(String phone){
try {
if (phone==null||"".equals(phone)){
return new Result(false,"手机号不能为空");
}
if (!PhoneFormatCheckUtils.isPhoneLegal(phone)){
return new Result(false,"手机号格式错误");
}
userService.sendCode(phone);
return new Result(true,"发送成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"发送失败");
}
}
//添加用户 验证验证码是否正确
@RequestMapping("/add")
/*
@RequestBody从页面接收数据
User user, String smscode参数名称要与页面相同
*/
public Result add(@RequestBody User user, String smscode){
try {
Boolean checkSmsCode = userService.checkSmsCode(user.getPhone(), smscode);
System.out.println(smscode);
if (!checkSmsCode){
return new Result(false,"验证码或者手机号不正确");
}
user.setStatus("Y");
user.setCreated(new Date());
user.setUpdated(new Date());
user.setSourceType("1");
userService.add(user);
return new Result(true,"注册成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"注册失败");
}
}
}
8. 编写消息服务器项目(spring boot)
- src/main/java下创建util包作为工具包并引入一个工具类SmsUtil,作后续使用
SmsUtil:
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class SmsUtil {
//产品名称:云通信短信API产品,开发者无需替换
static final String product = "Dysmsapi";
//产品域名,开发者无需替换
static final String domain = "dysmsapi.aliyuncs.com";
@Autowired
private Environment env;
// TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
/**
* 发送短信
* @param mobile 手机号
* @param template_code 模板号
* @param sign_name 签名
* @param param 参数
* @return
* @throws ClientException
*/
public SendSmsResponse sendSms(String mobile,String template_code,String sign_name,String param) throws ClientException {
String accessKeyId =env.getProperty("accessKeyId");
String accessKeySecret = env.getProperty("accessKeySecret");
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers(mobile);
//必填:短信签名-可在短信控制台中找到
request.setSignName(sign_name);
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(template_code);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam(param);
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
return sendSmsResponse;
}
}
- src/main/java下创建listener包并写一个监听类SmsListener
@Component
public class SmsListener {
@Autowired
private SmsUtil smsUtil;
//监听到消息发送方中间件sms
@JmsListener(destination = "sms")
public void sendSms(Map<String,String> map){
try{
SendSmsResponse response = smsUtil.sendSms(
map.get("mobile"),//手机号码
map.get("template_code"),//模板编号
map.get("sign_name"),//签名
map.get("param") );// 短信内容
System.out.println("Code=" + response.getCode());
System.out.println("Message=" + response.getMessage());
System.out.println("RequestId=" + response.getRequestId());
System.out.println("BizId=" + response.getBizId());
}catch (ClientException e){
e.printStackTrace();
}
}
}
- 编写启动类(在listener包和util包的上一级目录
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
9. 测试,启动顺序:zookeeper→Redis→启动类→service-user→web-user
10. 查看数据库
over~