第十二天

一、内容介绍

1.登录实现流程(单点登录):

image

2.实现项目中的注册接口

image
使用步骤:
1. 在common中引入依赖,复制jwt工具类:

点击查看代码jwt需要添加的依赖
    <dependencies>
        <!-- JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
    </dependencies>

2. 在common模块中创建JWT工具类

点击查看代码jwt的工具类
/*需要知道类中怎么去进行修改:*/
public class JwtUtils {
    /*常量EXPIRE:设置token的过期时间
     *APP_SECRET:密钥 (在公司中,一般是公司按照自己的加密规则生成) */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 生成token字符串的方法
     * @param id 用户id
     * @param nickname 用户名称
     * @return
     */
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()  //构建jwt字符串
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256") //设置头信息

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) //设置过期时间 :一般修改时修改setSubject和EXPIRE

                /*设置token的主体部分,也就是存储用户信息,如果有多个,可以添加多行*/
                .claim("id", id)
                .claim("nickname", nickname)

                /*签名哈希:*/
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效 :判断token是否是伪造的
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            /*通过这行代码进行判断token值是否有效*/
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request 通过头信息,获取token 值
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 根据token获取用户信息的id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        /*用来获取字符串中的主体部分:通过key 获取value */
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

3.新建短信微服务

整合阿里云短信服务,注册时候发送手机验证码

image

  1. 在service 创建子模块 service_msm
  2. 创建包的结构,创建controller 和 service 等结构
  3. 阿里云短信服务开通和使用 SMS_259450133
  4. 编写代码实现短息的发送
  • 在service-sms中引入依赖
短信服务模块添加的依赖
    <dependencies>
        <!--    这是一个json的转化工具    -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <!--    阿里云操作的核心的库    -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
        </dependency>
    </dependencies>
相关的配置application.properties
# 服务端口
server.port=8006
# 服务名
spring.application.name=service-msm
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/xiaofan_edu?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123

#Redis 的相关配置
spring.redis.host=192.168.149.128
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/xiaofan/msmservice/mapper/xml/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

设置生成随机数的工具类
/**
 * 获取随机数的工具类
 */
public class RandomUtil {
    private static final Random random = new Random();
    private static final DecimalFormat fourdf = new DecimalFormat("0000");
    private static final DecimalFormat sixdf = new DecimalFormat("000000");
    public static String getFourBitRandom() {
        return fourdf.format(random.nextInt(10000));
    }
    public static String getSixBitRandom() {
        return sixdf.format(random.nextInt(1000000));
    }
    /**
     * 给定数组,抽取n个数据
     *
     * @param list
     * @param n
     * @return
     */
    public static ArrayList getRandom(List list, int n) {
        Random random = new Random();
        HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
        // 生成随机数字并存入HashMap
        for (int i = 0; i < list.size(); i++) {
            int number = random.nextInt(100) + 1;
            hashMap.put(number, i);
        }
        // 从HashMap导入数组
        Object[] robjs = hashMap.values().toArray();
        ArrayList r = new ArrayList();
        // 遍历数组并打印数据
        for (int i = 0; i < n; i++) {
            r.add(list.get((int) robjs[i]));
            System.out.print(list.get((int) robjs[i]) + "\t");
        }
        System.out.print("\n");
        return r;
    }
}

编写Controller部分,里面实现了使用redis设置有效时常
@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin //解决跨越问题
public class MsmController {
    /*验证码在五分钟之内有效:怎么实现呢?
        Redis能够设置过期时间,因此可以使用redis来进行设置它的有效时间
        怎么做呢?
        1.注入:    private RedisTemplate<String,String> redisTemplate;
        2.从Redis中获取验证码,如果能够获取到直接返回
        3.如果获取不到,则进行阿里云短信发送
    */
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private MsmService msmService;

    @GetMapping("send/{phone}")
    public R sendMsm(@PathVariable String phone) {
        //2.从Redis中获取验证码,如果能够获取到直接返回
        String code = redisTemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
            return R.ok();
        }
        //3.如果获取不到,则进行阿里云短信发送
        //通过工具类,生成随机的数,传递给阿里云进行发送
        String randomCode = RandomUtil.getFourBitRandom();//生成随机数
        Map<String, Object> param = new HashMap<>();
        param.put("code", randomCode);

        //调用service中的方法进行短信的发送
        boolean isSend = msmService.send(param, phone);
        if (isSend) {
            System.out.println(isSend + "---------------------------------");

            //发送成功,把发送的验证码放到Redis中,并设置有效时间
            //randomCode:这里需要传进来生成的随机数,如果传code ,则可能为空
            redisTemplate.opsForValue().set(phone, randomCode, 5, TimeUnit.MINUTES);
            return R.ok();
        } else {
            return R.error().message("短信发送失败");
        }
    }
}

生成service代码
public interface MsmService {
    boolean send(Map<String, Object> param, String phone);
}
service 代码的实现类:在这里实现短信的发送功能
@Service
public class MsmServiceImpl implements MsmService {
    /**
     * 发送短信的方法: 
     *
     * @param param 包含验证码
     * @param phone 手机号
     * @return 返回Boolean值,True 则发送成功
     */
    @Override
    public boolean send(Map<String, Object> param, String phone) {
        if (StringUtils.isEmpty(phone)) return false; //判断电话是否为空
        DefaultProfile profile =
                DefaultProfile.getProfile("default", "LTAI5t5cpNhgU9e8PBo3nxqj", "VxmEJ1UnTi8jIwcPs1hd9vTlweyrz8");
        IAcsClient client = new DefaultAcsClient(profile);

        //设置一些相关的固定参数:
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST); //默认提交方式
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        //设置发送相关的一些参数:
        request.putQueryParameter("PhoneNumbers", phone); //设置发送的手机号
        request.putQueryParameter("SignName", "阿里云短信测试"); //签名的名称
        request.putQueryParameter("TemplateCode", "SMS_154950909"); //模板的Code
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param)); //设置验证码:JSON的格式

        //最终的发送
        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;
    }
}

**为什么使用Redis做短信验证的有效时间呢?** https://blog.csdn.net/weixin_53029342/article/details/127337567

4.登录接口

用户登录注册接口的实现:
1.在service模块下创建子模块service-ucenter
2.使用代码生成器生成代码 ,创建ucenter_member表

3.配置application.properties
# 服务端口
server.port=8006
# 服务名
spring.application.name=service-ucenter
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/xiaofan_edu?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123

#Redis 的相关配置
spring.redis.host=192.168.149.128
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/xiaofan/educenter/mapper/xml/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4.创建ServiceUcApplication.java
//@EnableDiscoveryClient //   服务器端
@SpringBootApplication
@ComponentScan({"com.xiaofan"}) //指定扫描位置
@MapperScan("com.xiaofan.educenter.mapper")
public class UcenterApplication {
    public static void main(String[] args) {
        SpringApplication.run(UcenterApplication.class, args);
    }
}

5.创建登录和注册接口以及token获取登录的信息
@RestController
@RequestMapping("/educenter/member")
@CrossOrigin
public class UcenterMemberController {
    @Autowired
    private UcenterMemberService memberService;

    /**
     * 登录功能 :使用token实现单点登录
     *
     * @param member UcenterMember中的对象
     * @return
     */
    //@GetMapping("login") //会出现这个错误:Required request body is missing:
    @PostMapping("login")
    public R login(@RequestBody UcenterMember member) {
        //通过service方法实现登录,返回token值,使用jwt生成
        String token = memberService.login(member);
        return R.ok().data("token", token);
    }

    /*
    注册:除了最基本的数据之外,还需要判断验证码的值:
      1.传对象的时候,不能够传ucenter的实体类,需要重新创建一个类(registeredVo),把验证码的属性封装注册进去
      2.然后在controller中开始创建注册该方法
      3.创建service中的方法
     */
    @PostMapping("register")
    public R registered(@RequestBody RegisteredVo register) {
        memberService.register(register);
        return R.ok();
    }

    /*根据token获取登录的信息:因为里面包含了用户的信息
        然后通过cookie在页面中进行显示:通过jwt的工具类来进行编写:

     */
    @GetMapping("getMemberInfo")
    public R getMemberInfo(HttpServletRequest request) {
        //调用JWT工具类的方法,根据request对象获取头信息,返回用户id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        //查询数据库根据用户id获取用户信息
        UcenterMember memberInfo = memberService.getById(memberId);
        return R.ok().data("userInfo", memberInfo);
    }
}

6.创建对应的service接口
public interface UcenterMemberService extends IService<UcenterMember> {
    String login(UcenterMember member);
    /*注册方法*/
    void register(RegisteredVo register);
}
7.注册方法中需要实现的实体类对象
@Data
public class RegisteredVo {
    @ApiModelProperty(value = "昵称")
    private String nickname;
    @ApiModelProperty(value = "手机号")
    private String mobile;
    @ApiModelProperty(value = "密码")
    private String password;
    @ApiModelProperty(value = "验证码")
    private String code;
}
8.实现接口中的方法
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /**
     * 登录的方法 :使用手机号进行登录 ,并判断是否禁用 1(true)已禁用,  0(false)未禁用
     *
     * @param member
     * @return
     */
    @Override
    public String login(UcenterMember member) {
        //获取手机号和密码
        String mobile = member.getMobile();
        String password = member.getPassword();

        //判断手机号和密码是否为空
        if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
            throw new XiaofanException(ResultCode.ERROR, "登录失败!");
        }

        //判断手机号是否正确
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile", mobile);
        UcenterMember member1 = baseMapper.selectOne(wrapper);
        if (member1 == null) { //判断查询的对象是否为空
            throw new XiaofanException(ResultCode.ERROR, "手机号未注册,请先注册");
        }

        //判断密码是否正确 :使用加密方式MD5进行判断
       /*存在问题:在存储密码的时候,存储的是加密的密码,不存储明码的密码,因此输入的密码和后台比较的密码不正确
          怎么进行处理呢?
          把自己写的密码同样也进行加密,然后在和数据库中的密码进行比较!
          一般加密方式!MD5 -->只能加密,不能解密
        */
        if (!MD5.encrypt(password).equals(member1.getPassword())) {
            throw new XiaofanException(ResultCode.ERROR, "密码错误");
        }

        //判断用户是否被禁用
        if (member1.getIsDisabled()) {
            throw new XiaofanException(ResultCode.ERROR, "该用户已经被禁用,请联系管理员申请");
        }

        //登录成功,获取token值
        String token = JwtUtils.getJwtToken(member1.getId(), member1.getNickname());
        return token;
    }
    /**
     * 注册的方法 :也就是把注册中的内容都添加到数据库中
     *
     * @param register
     */
    @Override
    public void register(RegisteredVo register) {
        //获取注册的数据
        String code = register.getCode();//获取验证码信息
        String password = register.getPassword();//密码
        String nickname = register.getNickname(); //用户名
        String mobile = register.getMobile(); //电话


        //进行非空判断
        if (StringUtils.isEmpty(code) || StringUtils.isEmpty(password) ||
                StringUtils.isEmpty(nickname) || StringUtils.isEmpty(mobile)) {
            throw new XiaofanException(ResultCode.ERROR, "注册失败,数据未填写完全!");
        }

        //判断手机的验证码是否正确:把输入的验证码和redis中的验证码进行比较
        //获取redis中的验证码:根据手机号进行获取
        String redisCode = redisTemplate.opsForValue().get(mobile);
        if (!code.equals(redisCode)) {
            throw new XiaofanException(ResultCode.ERROR, "验证码不正确!");
        }

        //判断手机号是否已经存在:存在相同的手机号,不进行添加!通过返回记录条数进行判断
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile", mobile);
        Integer count = baseMapper.selectCount(wrapper);
        if (count > 0) {
            throw new XiaofanException(ResultCode.ERROR, "手机号已经存在了");
        }

        //数据添加到数据库中:
        UcenterMember member = new UcenterMember();
        member.setNickname(nickname);
        member.setMobile(mobile);
        member.setPassword(MD5.encrypt(password));
        member.setIsDisabled(false);//用户不禁用
        member.setAvatar("https://img2.baidu.com/it/u=3431969610,2980762804&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1668963600&t=a42d68aa3032e83f0d1575f874c9d54a");
        baseMapper.insert(member);
    }
}

注意:在启动的时候,需要给时间和id属性添加自动注入的方法
    @ApiModelProperty(value = "会员id")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

5.登录和登录页面的前端实现

posted @ 2022-11-18 22:40  gz_xiaofan  阅读(147)  评论(0编辑  收藏  举报