尚筹网2-2.注册功能

1.总体思路

2.创建数据库表

create table t_member
(
   id                   int(11) not null auto_increment,
   loginacct            varchar(255) not null,
   userpswd             char(200) not null,
   username             varchar(255),
   email                varchar(255),
   authstatus           tinyint(4) comment '实名认证状态 0 - 未实名认证, 1 - 实名认证申请中, 2 - 已实名认证',
   usertype             tinyint(4) comment ' 0 - 个人, 1 - 企业',
   realname             varchar(255),
   cardnum              varchar(255),
   accttype             tinyint(4) comment '0 - 企业, 1 - 个体, 2 - 个人, 3 - 政府',
   primary key (id)
);

逆向工程生成相关资源。

※P.S.:两种Service对比

3.短信发送API调用

// 调用短信发送接口时的访问地址
String host = "https://feginesms.market.alicloudapi.com";

// 具体访问路径
String path = "/codeNotice";

// 请求方式
String method = "GET";

// 登录阿里云后,进入管理控制台->云市场->已购买服务,复制AppCode
String appcode = "61f2eaa3c43e42ad82c26fbbe1061311";
Map headers = new HashMap();

// 最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);

// 封装请求参数
Map querys = new HashMap();

// 验证码
querys.put("param", "7558");

// 接收短信的手机号
querys.put("phone", "13547119500");

// 签名编号
querys.put("sign", "1");

// 模板编号
querys.put("skin", "1");

// JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.zip

try {
    /**
     * 重要提示如下: HttpUtils请从
     * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
     * 或者直接下载: http://code.fegine.com/HttpUtils.zip 下载
     *
     * 相应的依赖请参照
     * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
     * 相关jar包(非pom)直接下载: http://code.fegine.com/aliyun-jar.zip
     */
    HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
    // System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。
    // 状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误
    // 获取response的body
    System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
    e.printStackTrace();
}
package com.aliyun.api.gateway.demo.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

public class HttpUtils {

    /**
     * get
     * 
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static HttpResponse doGet(String host, String path, String method, 
                                     Map headers, 
                                     Map querys)
        throws Exception {      
        HttpClient httpClient = wrapClient(host);

        HttpGet request = new HttpGet(buildUrl(host, path, querys));
        for (Map.Entry e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        return httpClient.execute(request);
    }

    /**
     * post form
     * 
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param bodys
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method, 
                                      Map headers, 
                                      Map querys, 
                                      Map bodys)
        throws Exception {      
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (bodys != null) {
            List nameValuePairList = new ArrayList();

            for (String key : bodys.keySet()) {
                nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
            request.setEntity(formEntity);
        }

        return httpClient.execute(request);
    }   

    /**
     * Post String
     * 
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method, 
                                      Map headers, 
                                      Map querys, 
                                      String body)
        throws Exception {      
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Post stream
     * 
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method, 
                                      Map headers, 
                                      Map querys, 
                                      byte[] body)
        throws Exception {      
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Put String
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, String method, 
                                     Map headers, 
                                     Map querys, 
                                     String body)
        throws Exception {      
        HttpClient httpClient = wrapClient(host);

        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Put stream
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, String method, 
                                     Map headers, 
                                     Map querys, 
                                     byte[] body)
        throws Exception {      
        HttpClient httpClient = wrapClient(host);

        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Delete
     *  
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static HttpResponse doDelete(String host, String path, String method, 
                                        Map headers, 
                                        Map querys)
        throws Exception {      
        HttpClient httpClient = wrapClient(host);

        HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
        for (Map.Entry e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        return httpClient.execute(request);
    }

    private static String buildUrl(String host, String path, Map querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }                   
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }

    private static HttpClient wrapClient(String host) {
        HttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) {
            sslClient(httpClient);
        }

        return httpClient;
    }

    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] xcs, String str) {

                }
                public void checkServerTrusted(X509Certificate[] xcs, String str) {

                }
            };
            ctx.init(null, new TrustManager[] { tm }, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }
}

    com.alibaba
    fastjson
    1.2.15


    org.apache.httpcomponents
    httpclient
    4.2.1


    org.apache.httpcomponents
    httpcore
    4.2.1


    commons-lang
    commons-lang
    2.6


    org.eclipse.jetty
    jetty-util
    9.3.7.v20160115

4.封装发送短信的工具方法

①加入依赖

[1]distribution-crowd-parent工程

    com.alibaba
    fastjson
    1.2.15


    commons-lang
    commons-lang
    2.6


    org.eclipse.jetty
    jetty-util
    9.3.7.v20160115

httpclient和httpcore在项目中本身已经加入,不再重复加入,避免jar包冲突。

[2]distribution-crowd-1-common工程

    com.alibaba
    fastjson


    org.apache.httpcomponents
    httpclient


    org.apache.httpcomponents
    httpcore


    commons-lang
    commons-lang


    org.eclipse.jetty
    jetty-util

②加入HttpUtils类

参见上面的笔记

③创建工具方法类

public class CrowdUtils {
    /**
     * 验证集合是否有效
     * @param c     待验证集合
     * @return      验证结果(true:有效,false:无效)
     * @author 封捷
     */
    public static  boolean collectionEffectiveCheck(Collection c) {
        return (c != null) && (c.size() > 0);
    }
    
    /**
     * 验证字符串是否有效
     * @param source    待验证字符串
     * @return          验证结果(true:有效,false:无效)
     * @author 封捷
     */
    public static boolean strEffectiveCheck(String source) {
        return (source != null) && (source.length() > 0);
    }
    
    /**
     * 生成随机验证码
     * @param length    验证码长度
     * @return          生成的验证码
     * @throws  RuntimeException 验证码长度必须大于0
     * @author 封捷
     */
    public static String randomCode(int length) {
        
        if(length <= 0) {
            throw new RuntimeException(CrowdConstant.MESSAGE_RANDOM_CODE_LENGTH_INVALID);
        }
        
        StringBuilder builder = new StringBuilder();
        
        for(int i = 0; i < length; i++) {
            
            // 1.生成随机数
            double doubleRandom = Math.random();
            
            // 2.调整
            int integerRandom = (int) (doubleRandom * 10);
            
            // 3.拼接
            builder.append(integerRandom);
        }
        
        return builder.toString();
    }
        
    /**
     * 发送验证码短信
     * @param appcode       阿里云市场中调用API时识别身份的appCode
     * @param randomCode    验证码值
     * @param phoneNum      接收验证码短信的手机号
     */
    public static void sendShortMessage(String appcode, String randomCode, String phoneNum) {
        // 调用短信发送接口时的访问地址
        String host = "https://feginesms.market.alicloudapi.com";
        
        // 具体访问路径
        String path = "/codeNotice";
        
        // 请求方式
        String method = "GET";
        
        // 登录阿里云后,进入管理控制台->云市场->已购买服务,复制AppCode
        // String appcode = "61f2eaa3c43e42ad82c26fbbe1061311";
        Map headers = new HashMap();
        
        // 最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
        headers.put("Authorization", "APPCODE " + appcode);
        
        // 封装请求参数
        Map querys = new HashMap();
        
        // 验证码
        querys.put("param", randomCode);
        
        // 接收短信的手机号
        querys.put("phone", phoneNum);
        
        // 签名编号
        querys.put("sign", "1");
        
        // 模板编号
        querys.put("skin", "1");
        
        // JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.zip
        
        try {
            /**
             * 重要提示如下: HttpUtils请从
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
             * 或者直接下载: http://code.fegine.com/HttpUtils.zip 下载
             *
             * 相应的依赖请参照
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
             * 相关jar包(非pom)直接下载: http://code.fegine.com/aliyun-jar.zip
             */
            HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
            // System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。
            // 状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误
            // 获取response的body
            System.out.println(EntityUtils.toString(response.getEntity()));
        } catch (Exception e) {
            e.printStackTrace();
            
            throw new RuntimeException(e.getMessage());
        }
    }

}

5.ResultEntity

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultEntity {
    
    private String result;
    private String message;
    private T data;
    
    public static final String SUCCESS = "SUCCESS";
    public static final String FAILED = "FAILED";
    public static final String NO_MSG = "NO_MSG";
    public static final String NO_DATA = "NO_DATA";
    
    public static ResultEntity successNoData() {
        return new ResultEntity<>(SUCCESS, NO_MSG, NO_DATA);
    }
    
    public static  ResultEntity successWithData(T data) {
        return new ResultEntity<>(SUCCESS, NO_MSG, data);
    }
    
    public static  ResultEntity failed(String message) {
        return new ResultEntity<>(FAILED, message, null);
    }

}

6.创建发送短信验证码的handler方法

①Redis中保存验证码数据的key

使用用户接受验证码的手机号进行区分。
格式:RANDOM_CODE_[手机号]
例如:RANDOM_CODE_13512345678
KEY VALUE
RANDOM_CODE_13512345678 7754
RANDOM_CODE_18843218765 6631
…… ……
所以可以将前缀保存到常量中
public static final String REDIS_RANDOM_CODE_PREFIX = "RANDOM_CODE_";

②声明redis-provider功能的接口

所在工程:distribution-crowd-1-common

接口全类名:com.rgh.crowd.api.RedisOperationRemoteService

[1]需要加入依赖

    org.springframework.cloud
    spring-cloud-starter-openfeign

[2]声明接口

Redis提供的功能只需要针对String类型的key和value来进行操作即可。

@FeignClient(value="redis-provider")
public interface RedisOperationRemoteService {

    /**
     * 将字符串类型的键值对保存到Redis时调用的远程方法
     * @param normalKey
     * @param normalValue
     * @param timeoutMinute 超时时间(单位:分钟)
     *      -1表示无过期时间
     *      正数表示过期时间分钟数
     *      0和null值不接受
     * @return
     */
    @RequestMapping("/save/normal/string/key/value")
    ResultEntity saveNormalStringKeyValue(@RequestParam("normalKey") String normalKey, @RequestParam("normalValue") String normalValue, @RequestParam("timeoutMinute") Integer timeoutMinute);

    /**
     * 根据key查询对应value时调用的远程方法
     * @param normalKey
     * @return
     */
    @RequestMapping("/retrieve/string/value/by/string/key")
    ResultEntity retrieveStringValueByStringKey(@RequestParam("normalKey") String normalKey);

    /**
     * 根据key删除对应value时调用的远程方法
     * @param key
     * @return
     */
    @RequestMapping("/redis/remove/by/key")
    ResultEntity removeByKey(@RequestParam("key") String key);

}

③redis-provider功能实现

所在工程:distribution-crowd-4-redis-provider

全类名:com.rgh.crowd.controller.RedisOperationController

友情提示:RedisOperationController不需要也不建议实现RedisOperationRemoteService接口。

@RestController
public class RedisOperationController {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 将字符串类型的键值对保存到Redis时调用的远程方法
     * @param normalKey
     * @param normalValue
     * @param timeoutMinute 超时时间(单位:分钟)
     * @return
     */
    @RequestMapping("/save/normal/string/key/value")
    ResultEntity saveNormalStringKeyValue(
            @RequestParam("normalKey") String normalKey, 
            @RequestParam("normalValue") String normalValue, 
            @RequestParam("timeoutMinute") Integer timeoutMinute) {
        
        // 对输入数据进行验证
        if(!CrowdUtils.strEffectiveCheck(normalKey) || !CrowdUtils.strEffectiveCheck(normalValue)) {
            return ResultEntity.failed(CrowdConstant.MESSAGE_REDIS_KEY_OR_VALUE_INVALID);
        }
        
        // 获取字符串操作器对象
        ValueOperations operator = redisTemplate.opsForValue();
        
        // 判断timeoutMinute值:是否为无效值
        if(timeoutMinute == null || timeoutMinute == 0) {
            return ResultEntity.failed(CrowdConstant.MESSAGE_REDIS_KEY_TIME_OUT_INVALID);
        }
        
        // 判断timeoutMinute值:是否为不设置过期时间
        if(timeoutMinute == -1) {
            
            // 按照不设置过期时间的方式执行保存
            try {
                operator.set(normalKey, normalValue);
            } catch (Exception e) {
                e.printStackTrace();
                
                return ResultEntity.failed(e.getMessage());
            }
            
            // 返回结果
            return ResultEntity.successNoData();
        }
        
        // 按照设置过期时间的方式执行保存
        try {
            operator.set(normalKey, normalValue, timeoutMinute, TimeUnit.MINUTES);
        } catch (Exception e) {
            e.printStackTrace();
            
            return ResultEntity.failed(e.getMessage());
        }
        
        return ResultEntity.successNoData();
    }
    
    /**
     * 根据key查询对应value时调用的远程方法
     * @param normalKey
     * @return
     */
    @RequestMapping("/retrieve/string/value/by/string/key")
    ResultEntity retrieveStringValueByStringKey(@RequestParam("normalKey") String normalKey) {
        
        // 对输入数据进行验证
        if(!CrowdUtils.strEffectiveCheck(normalKey)) {
            return ResultEntity.failed(CrowdConstant.MESSAGE_REDIS_KEY_OR_VALUE_INVALID);
        }
        
        try {
            String value = redisTemplate.opsForValue().get(normalKey);
            
            return ResultEntity.successWithData(value);
        } catch (Exception e) {
            e.printStackTrace();
            
            return ResultEntity.failed(e.getMessage());
        }
        
    }
    
    /**
     * 根据key删除对应value时调用的远程方法
     * @param key
     * @return
     */
    @RequestMapping("/redis/remove/by/key")
    ResultEntity removeByKey(@RequestParam("key") String key) {
        
        // 对输入数据进行验证
        if(!CrowdUtils.strEffectiveCheck(key)) {
            return ResultEntity.failed(CrowdConstant.MESSAGE_REDIS_KEY_OR_VALUE_INVALID);
        }
        
        try {
            redisTemplate.delete(key);
            
            return ResultEntity.successNoData();
        } catch (Exception e) {
            e.printStackTrace();
            
            return ResultEntity.failed(e.getMessage());
        }
        
    }

}

④member-manager的handler方法

[1]注意
  • 主启动类上需要写@EnableFeignClients注解
  • @Autowired装配RedisOperationRemoteService
[2]代码
@RequestMapping("/member/manager/send/code")
public ResultEntity sendCode(@RequestParam("phoneNum") String phoneNum) {
    
    if(!CrowdUtils.strEffectiveCheck(phoneNum)) {
        return ResultEntity.failed(CrowdConstant.MESSAGE_PHONE_NUM_INVALID);
    }
    
    // 1.生成验证码
    int length = 4;
    String randomCode = CrowdUtils.randomCode(length);
    
    // 2.保存到Redis
    Integer timeoutMinute = 15;
    
    String normalKey = CrowdConstant.REDIS_RANDOM_CODE_PREFIX + phoneNum;
    
    ResultEntity resultEntity = redisRemoteService.saveNormalStringKeyValue(normalKey, randomCode, timeoutMinute);
    
    if(ResultEntity.FAILED.equals(resultEntity.getResult())) {
        return resultEntity;
    }
    
    // 3.发送验证码到用户手机
    String appcode = "61f2eaa3c43e42ad82c26fbbe1061311";
    try {
        CrowdUtils.sendShortMessage(appcode, randomCode, phoneNum);
        
        return ResultEntity.successNoData();
    } catch (Exception e) {
        e.printStackTrace();
        
        return ResultEntity.failed(e.getMessage());
    }
    
}

7.分布式事务

①举例

发送短信操作和Redis保存操作在执行时都有可能失败。如果发生其中一个操作失败,另一个成功,那么数据整体状态就会发生不一致的情况。

②分析

如果是在以前关系型数据库的事务操作中,可以采用回滚机制,一个事务多个操作中有任何一个失败,则整个事务全部回滚。

在分布式环境下,没办法将多个具体操作纳入到一个统一的事务中,一起提交、一起回滚。

也不能使用逆操作手动撤销,因为逆操作除了功能和原本操作不同,其他方面和原本的操作性质是一样的。

③解决办法

8.从yml配置文件中读取appCode

①yml配置

这些配置项节点全部都是我们自定义的,完全没有框架的支持。这是允许的。

crowd:
  short:
    message:
      appCode: 61f2eaa3c43e42ad82c26fbbe1061311

②在Java类中引用方式

@Value("${crowd.short.message.appCode}")
private String appCode;

意义:以后在SpringBoot环境下开发时,如果某些信息不能在Java代码中写死,就可以使用这样的机制在yml配置文件中配置,再使用@Value注解读取。yml或properties文件都可以。

9.执行注册

①流程分析

  • 接收表单数据
    • 登录账号
    • 密码
    • 手机号
    • 验证码
  • 检查验证码是否有效
    • 无效:返回失败信息,停止执行
    • 有效:继续执行
  • 检查手机号是否有效
    • 无效:返回失败信息,停止执行
    • 有效:继续执行
  • 拼接随机验证码的KEY
  • 远程调用redis-provider的方法获取对应的验证码值
    • 没有查询到值:返回失败信息,停止执行
    • 查询到有效值:继续执行
  • 进行比较
    • 表单提交的验证码
    • Redis取回的验证码
  • 不一致:返回失败信息,停止执行
  • 一致
    • 从Redis中删除当前KEY对应的验证码
    • 继续执行
  • 远程调用database-provider方法检查登录账号是否被占用
    • 已经被占用:返回失败信息,停止执行
    • 没有被占用:继续执行
  • 密码加密
  • 远程调用database-provider方法执行保存操作

②封装MemberVO

所在工程:distribution-crowd-1-common

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberVO {
    
    private String loginacct;
    
    private String userpswd;
    
    private String phoneNum;
    
    private String randomCode;

}

⑤在database-provider中创建其他组件

  • MemberController
  • MemberService
  • MemberServiceImpl

⑥在database-provider中查找登录账号数量

[1]接口声明

所在工程:distribution-crowd-1-common

@FeignClient("database-provider")
public interface DataBaseOperationRemoteService {
    
    @RequestMapping("/retrieve/loign/acct/count")
    ResultEntity retrieveLoignAcctCount(@RequestParam("loginAcct") String loginAcct);

}
[2]database-provider中和接口方法对应的Controller方法
@RequestMapping("/retrieve/loign/acct/count")
public ResultEntity retrieveLoignAcctCount(
        @RequestParam("loginAcct") String loginAcct) {
    
    if(!CrowdUtils.strEffectiveCheck(loginAcct)) {
        return ResultEntity.failed(CrowdConstant.MESSAGE_LOGINACCT_INVALID);
    }
    
    try {
        int count = memberService.getLoginAcctCount(loginAcct);
        
        return ResultEntity.successWithData(count);
        
    } catch (Exception e) {
        e.printStackTrace();
        
        return ResultEntity.failed(e.getMessage());
    }
}
[3]database-provider中Controller方法调用的Service方法
@Override
public Integer getLoignAcctCount(String loginAcct) {
    
    // 1.创建MemberPOExample
    MemberPOExample example = new MemberPOExample();
    
    // 2.封装查询条件
    example.createCriteria().andLoginacctEqualTo(loginAcct);
    
    // 3.执行查询
    return memberPOMapper.countByExample(example);
}

⑦在database-provider中保存Member

[1]接口方法声明

所在工程:distribution-crowd-1-common

所在接口:com.rgh.crowd.api.DataBaseOperationRemoteService

@RequestMapping("/save/member/remote")
ResultEntity saveMemberRemote(@RequestBody MemberPO memberPO);

注意:一定要写@RequestBody注解,如果没写数据就传不过来。

[2]和接口对应的Controller方法

所在工程:distribution-crowd-3-database-provider

所在类:com.rgh.crowd.controller.MemberController

@RequestMapping("/save/member/remote")
public ResultEntity saveMemberRemote(@RequestBody MemberPO memberPO) {
    
    try {
        // 执行保存
        memberService.saveMemberPO(memberPO);
    } catch (Exception e) {
        e.printStackTrace();
        
        return ResultEntity.failed(e.getMessage());
    }
    
    return ResultEntity.successNoData();
}

Service方法

@Override
@Transactional(readOnly=false, propagation=Propagation.REQUIRES_NEW, rollbackFor=Exception.class)
public void saveMemberPO(MemberPO memberPO) {
    memberPOMapper.insert(memberPO);
}

如果没有设置readOnly=false,那么会遇到下面异常:

{
    "result": "FAILED",
    "message": "\r\n### Error updating database.  Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed\r\n### The error may involve com.atguigu.crowd.mapper.MemberPOMapper.insert-Inline\r\n### The error occurred while setting parameters\r\n### SQL: insert into t_member (id, loginacct, userpswd,        username, email, authstatus,        usertype, realname, cardnum,        accttype)     values (?, ?, ?,        ?, ?, ?,        ?, ?, ?,        ?)\r\n### Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed\n; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed",
    "data": null
}

⑧密码加密处理

使用SpringSecurity中的BCryptPasswordEncoder加密工具进行密码加密和密码比较。

[1]创建配置类

所在工程:distribution-crowd-5-member-manager

全类名:com.rgh.crowd.config.CrowdConfig

package com.atguigu.crowd.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class CrowdConfig {
    
    @Bean
    public BCryptPasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
[2]在需要的地方装配
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

⑨最终注册逻辑代码

所在工程:distribution-crowd-5-member-manager

所在类:com.rgh.crowd.controller.MemberController

@RequestMapping("/member/register")
public ResultEntity register(@RequestBody MemberVO memberVO) {

    // 1.获取验证码数据并进行有效性检测
    String randomCode = memberVO.getRandomCode();

    if(!CrowdUtils.strEffectiveCheck(randomCode)) {
        return ResultEntity.failed(CrowdConstant.MESSAGE_RANDOM_CODE_INVALID);
    }

    // 2.获取手机号数据并进行有效性检测
    String phoneNum = memberVO.getPhoneNum();

    if(!CrowdUtils.strEffectiveCheck(phoneNum)) {
        return ResultEntity.failed(CrowdConstant.MESSAGE_PHONENUM_INVALID);
    }

    // 3.拼接Redis存储验证码的KEY
    String randomCodeKey = CrowdConstant.REDIS_RANDOM_CODE_PREFIX + phoneNum;

    // 4.远程调用redis-provider的方法查询对应验证码
    ResultEntity randomCodeRemoteResultEntity = redisOperationRemoteService.retrieveRandomCodeRemote(randomCodeKey);

    if(ResultEntity.FAILED.equals(randomCodeRemoteResultEntity.getResult())) {
        return randomCodeRemoteResultEntity;
    }

    // 5.检查远程获取的验证码是否存在
    String randomCodeRemote = randomCodeRemoteResultEntity.getData();

    if(!CrowdUtils.strEffectiveCheck(randomCodeRemote)) {

        return ResultEntity.failed(CrowdConstant.MESSAGE_RANDOM_CODE_OUT_OF_DATE);

    }

    // 6.将“表单验证码”和“Redis验证码”进行比较
    if(!Objects.equal(randomCode, randomCodeRemote)) {
        return ResultEntity.failed(CrowdConstant.MESSAGE_RANDOM_CODE_NOT_MATCH);
    }

    // 7.检测登录账号是否被占用
    String loginacct = memberVO.getLoginacct();
    ResultEntity loignAcctCountResultEntity = dataBaseOperationRemoteService.retrieveLoignAcctCount(loginacct);

    if(ResultEntity.FAILED.equals(loignAcctCountResultEntity.getResult())) {
        return randomCodeRemoteResultEntity;
    }

    Integer loignAcctCount = loignAcctCountResultEntity.getData();

    if(loignAcctCount > 0) {
        return ResultEntity.failed(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
    }

    // 8.加密
    String userpswd = memberVO.getUserpswd();

    if(!CrowdUtils.strEffectiveCheck(userpswd)) {
        return ResultEntity.failed(CrowdConstant.MESSAGE_PASSWORD_INVALID);
    }

    userpswd = passwordEncoder.encode(userpswd);
    memberVO.setUserpswd(userpswd);

    // 9.将VO对象转换为PO对象
    MemberPO memberPO = new MemberPO();
    BeanUtils.copyProperties(memberVO, memberPO);

    // 10.执行保存操作
    ResultEntity saveMemberRemoteResultEntity = dataBaseOperationRemoteService.saveMemberRemote(memberPO);

    return saveMemberRemoteResultEntity;
}

测试

你可能感兴趣的:(尚筹网2-2.注册功能)