• 【业务功能篇94】微服务-springcloud-springboot-认证服务-注册功能-第三方短信验证API


    商城认证服务

    一、搭建认证服务环境

      结合我们前面介绍的商城的架构我们需要单独的搭建一个认证服务。

    image.png

    1.创建项目

      首先创建一个SpringBoot项目,然后添加对应的依赖

    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.4.12version>
            <relativePath/> 
        parent>
        <groupId>com.msb.mallgroupId>
        <artifactId>mall-auth-serverartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>mall-auth_servername>
        <description>认证服务description>
        <properties>
            <java.version>1.8java.version>
            <spring-cloud.version>2020.0.1spring-cloud.version>
        properties>
        <dependencies>
            
            <dependency>
                <groupId>com.msb.mallgroupId>
                <artifactId>mall-commonsartifactId>
                <version>0.0.1-SNAPSHOTversion>
                <exclusions>
                    <exclusion>
                        <groupId>com.baomidougroupId>
                        <artifactId>mybatis-plus-boot-starterartifactId>
                    exclusion>
                exclusions>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-thymeleafartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloudgroupId>
                    <artifactId>spring-cloud-dependenciesartifactId>
                    <version>${spring-cloud.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
            dependencies>
        dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombokgroupId>
                                <artifactId>lombokartifactId>
                            exclude>
                        excludes>
                    configuration>
                plugin>
            plugins>
        build>
    
    project>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    2.注册中心配置

      我们需要把认证服务注册到Nacos中,添加对应的依赖,然后完成对应的配置

    # Nacos服务注册
    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 192.168.56.100:8848
      application:
        name: mall-auth_server
      # 统一的全局的--设置服务器响应给客户端的日期时间格式
      jackson:
        date-format: yyyy-MM-dd HH:mm:ss
      thymeleaf:
        cache: false # 关闭Thymeleaf的缓存
    server:
      port: 30000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    放开Nacos注册中心

    image.png

    然后启动测试

    image.png

    3.登录和注册页面

      然后我们整理登录和注册的相关资源,首先把登录和注册的模板文件拷贝进项目

    image.png

    然后把对应的静态文件拷贝到Nginx中。

    image.png

    然后我们需要在host文件中添加对应的配置

    image.png

    修改Nginx的反向代理的配置

    image.png

    然后重启Nginx的服务image.png

    然后修改网关服务

    image.png

    最后调整登录和注册页面的静态资源文件的路径.

    注册服务的名称:msb-auth,启动对应的服务,测试

    登录页面

    image.png

    注册页面

    image.png

    4.注册功能

    4.1 手机验证码

      先处理验证码的页面,使其能够倒数操作

    image.png

    image.png

    JS代码

    $(function(){
    				$("#sendCode").click(function(){
    					if($(this).hasClass("d1")){
    						// 说明正在倒计时
    					}else{
    						// 给指定的手机号发送验证码
    						timeoutChangeStyle()
    					}
    
    				});
    			})
    			var num = 10
    			function timeoutChangeStyle(){
    				$("#sendCode").attr("class","d1")
    				if(num == 0){
    					// 说明1分钟到了,可以再次发送验证码了
    					$("#sendCode").text("发送验证码")
    					num= 10;
    					$("#sendCode").attr("class","")
    				}else{
    					setTimeout('timeoutChangeStyle()',1000)
    					$("#sendCode").text(num+"s后再次发送")
    				}
    
    				num --;
    			}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4.2 短信验证接口

      通过阿里云的短信服务来实现。我们直接购买0元15次就可以了。https://www.aliyun.com/

    image.png

    image.png

    进入到对应的管理控制台,查看对应的信息

    image.png

    通过短信供应商提供的相关的编程语言的开发模板开发即可

    image.png

    供应商提供了对应的HttpUtils工具类,我们需要下载保存到我们自己的项目中。

    image.png

    然后封装对应的发送验证码的接口

    package com.msb.mall.third.utils;
    
    import lombok.Data;
    import org.apache.http.HttpResponse;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 短信组件
     */
    @ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
    @Data
    @Component
    public class SmsComponent {
    
        private String host;
        private String path;
        private String method = "POST";
        private String appCode;
    
        /**
         * 发送短信验证码
         * @param phone 发送的手机号
         * @param code 发送的短信验证码
         */
        public void sendSmsCode(String phone,String code){
            Map<String, String> headers = new HashMap<String, String>();
            //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
            headers.put("Authorization", "APPCODE " + appCode);
            //根据API的要求,定义相对应的Content-Type
            headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            Map<String, String> querys = new HashMap<String, String>();
            Map<String, String> bodys = new HashMap<String, String>();
            bodys.put("content", "code:"+code);
            bodys.put("phone_number", phone);
            bodys.put("template_id", "TPL_0000");
    
    
            try {
                HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
                System.out.println(response.toString());
                //获取response的body
                //System.out.println(EntityUtils.toString(response.getEntity()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    添加对应的属性信息

    image.png

    image.png

    4.3 短信功能

      然后我们将短信功能串联起来

    image.png

    4.3.1 third服务

      我们需要在第三方服务中提供对外的接口服务

    @RestController
    public class SMSController {
    
        @Autowired
        private SmsComponent smsComponent;
    
        /**
         * 调用短信服务商提供的短信API发送短信
         * @param phone
         * @param code
         * @return
         */
        @GetMapping("/sms/sendcode")
        public R sendSmsCode(@RequestParam("phone") String phone,@RequestParam("code") String code){
            smsComponent.sendSmsCode(phone,code);
            return R.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image.png

    4.3.2 认证服务

      我们需要在认证服务中通过feign来调用third中提供的短信服务,同时给客户端提供访问的接口

            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-loadbalancerartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    放开注解

    image.png

    然后声明Feign的接口

    image.png

    定义对应的Controller

    @Controller
    public class LoginController {
    
    
        @Autowired
        private ThirdPartFeginService thirdPartFeginService;
    
        @ResponseBody
        @GetMapping("/sms/sendCode")
        public R sendSmsCode(@RequestParam("phone") String phone){
            // 生成随机的验证码
            String code = UUID.randomUUID().toString().substring(0, 5);
            thirdPartFeginService.sendSmsCode(phone,code);
            return R.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    4.3.3 客户端

      然后我们需要在页面中通过jQuery的异步提交来发送短信

    image.png

    4.3.4 验证码存储

      当我们把验证码通过短信的形式发送给你客户手机,然后我们需要把手机号和对应的验证码存储起来,后面可能会集群部署,这时我们把这个信息存在在Redis中。

    image.png

    添加配置

    image.png

    存储数据

    image.png

    image.png

    搞定

    4.3.5 60秒间隔

      针对验证码发送的间隔必须是60秒以上,这时我们可以在保存到Redis中的数据的值我们加上发送的时间来处理

    image.png

    模板页面也需要做出对应的处理

    image.png

    4.4 注册数据验证

      表单提交的注册数据我们通过JSR303来验证。

    首先定义VO对象

    package com.msb.mall.vo;
    
    import lombok.Data;
    import org.hibernate.validator.constraints.Length;
    
    import javax.validation.constraints.NotEmpty;
    import javax.validation.constraints.Pattern;
    
    /**
     * 注册用户的VO对象
     */
    @Data
    public class UserRegisterVo {
    
        @NotEmpty(message = "账号不能为空")
        @Length(min = 3,max = 15,message = "账号必须是3~15位")
        private String userName; // 账号
    
        @NotEmpty(message = "密码不能为空")
        @Length(min = 3,max = 15,message = "密码必须是3~15位")
        private String password; // 密码
    
        @NotEmpty(message = "手机号不能为空")
        @Pattern(regexp = "^[1][3-9][0-9]{9}$",message = "手机号不合法")
        private String phone;  // 手机号
    
        @NotEmpty(message = "验证码不能为空")
        private String code;  // 验证码
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    然后就是控制器的逻辑代码

        @PostMapping("/sms/register")
        public String register(@Valid UserRegisterVo vo, BindingResult result, Model model){
            if(result.hasErrors()){
                // 表示提交的数据不合法
                List<FieldError> fieldErrors = result.getFieldErrors();
                Map<String,String> map = new HashMap<>();
                for (FieldError fieldError : fieldErrors) {
                    String field = fieldError.getField();
                    String defaultMessage = fieldError.getDefaultMessage();
                    map.put(field,defaultMessage);
                }
                model.addAttribute("error",map);
                return "/reg";
            }
            // 表单提交的注册的数据是合法的
    
            return "redirect:/login.html";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    然后就是页面代码处理

    image.png

    image.png

    验证码的校验

    image.png

    4.5 完整的注册功能

    image.png

    会员服务处理

    控制器

       /**
         * 会员注册
         * @return
         */
        @PostMapping("/register")
        public R register(@RequestBody MemberReigerVO vo){
            try {
                memberService.register(vo);
            }catch (UsernameExsitException exception){
                return R.error(BizCodeEnume.USERNAME_EXSIT_EXCEPTION.getCode(),
                        BizCodeEnume.USERNAME_EXSIT_EXCEPTION.getMsg());
            }catch (PhoneExsitExecption exsitExecption) {
                return R.error(BizCodeEnume.PHONE_EXSIT_EXCEPTION.getCode(),
                        BizCodeEnume.PHONE_EXSIT_EXCEPTION.getMsg());
            }catch (Exception e){
                return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),
                        BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
            }
    
            return R.ok();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    然后对应的Service

       /**
         * 完成会员的注册功能
         * @param vo
         */
        @Override
        public void register(MemberReigerVO vo) throws PhoneExsitExecption,UsernameExsitException{
            MemberEntity entity = new MemberEntity();
            // 设置会员等级 默认值
            MemberLevelEntity memberLevelEntity = memberLevelService.queryMemberLevelDefault();
            entity.setLevelId(memberLevelEntity.getId()); // 设置默认的会员等级
    
            // 添加对应的账号和手机号是不能重复的
            checkUsernameUnique(vo.getUserName());
            checkPhoneUnique(vo.getPhone());
    
            entity.setUsername(vo.getUserName());
            entity.setMobile(vo.getPhone());
    
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String encode = encoder.encode(vo.getPassword());
            // 需要对密码做加密处理
            entity.setPassword(encode);
             // 设置其他的默认值
            this.save(entity);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    auth服务通过Fegin远程调用

    image.png

    远程调用

        @PostMapping("/sms/register")
        public String register(@Valid UserRegisterVo vo, BindingResult result, Model model){
            Map<String,String> map = new HashMap<>();
            if(result.hasErrors()){
                // 表示提交的数据不合法
                List<FieldError> fieldErrors = result.getFieldErrors();
    
                for (FieldError fieldError : fieldErrors) {
                    String field = fieldError.getField();
                    String defaultMessage = fieldError.getDefaultMessage();
                    map.put(field,defaultMessage);
                }
                model.addAttribute("error",map);
                return "/reg";
            }else{
                // 验证码是否正确
                 String code = (String)redisTemplate.opsForValue().get(SMSConstant.SMS_CODE_PERFIX + vo.getPhone());
                code = code.split("_")[0];
                if(!code.equals(vo.getCode())){
                    // 说明验证码不正确
                    map.put("code","验证码错误");
                    model.addAttribute("error",map);
                    return "/reg";
                }else{
                    // 验证码正确  删除验证码
                    redisTemplate.delete(SMSConstant.SMS_CODE_PERFIX + vo.getPhone());
                    // 远程调用对应的服务 完成注册功能
                    R r = memberFeginService.register(vo);
                    if(r.getCode() == 0){
                        // 注册成功
                        return "redirect:http://msb.auth.com/login.html";
                    }else{
                        // 注册失败
                        map.put("msg",r.getCode()+":"+r.get("msg"));
                        model.addAttribute("error",map);
                        return "/reg";
                    }
                }
            }
    
    
    
            //return "redirect:/login.html";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    完成对应的服务注册

    image.png

    校验提示

    image.png

  • 相关阅读:
    【已解决】The ‘chartInit‘ function makes the dependencies of useEffect Hook……
    [计算机提升] 数据及相关概念
    Window部署Jaeger
    【Linux】常用的Linux命令(初学者必读)
    2023mathorcup大数据竞赛C题第一问求解思路用”时间序列分析法中的'平均法'“
    [Java Framework] [Spring] Spring Event / 事件的使用 一: ApplicationEvent
    mysql源码分析——InnoDB的内存结构源码
    ubuntu18安装coova chilli精简
    年龄越大,越要小心逢九年
    Vue defineProps 与 props
  • 原文地址:https://blog.csdn.net/studyday1/article/details/132637223