微服务项目实战技术点汇总:“尚硅谷的谷粒在线教育”八、实现Token单点登陆,微信二维码登陆,阿里短信微服务(验证码),整合JWT,MD5密码加密,OAuth2.0

文章目录

一、概念介绍

1、单点登陆(简称SSO)

单点登陆(SSO)
单点登陆就是,当在某个模块中登陆后,转到本项目其它模块时,无需二次登陆
单点登陆,适用于分布式系统,也就是我们当前这个项目,每个模块单独部署到一台服务器
常见OSS方式
session广播机制
通过为每一个模块复制当前登陆后的session对象实现
缺点:目前一个项目一般都有多个模块,每次登陆都要复制一次,及其浪费资源
cookie+redis
当我们在某一个模块登陆后,会把数据放到cookie和redis中
其中redis会生成唯一key值,对应value中会存储用户数据
最后把redis中的key值放到cookie里面
每次发送请求,都会带cookie对象,通过解析cookie中的key,获取redis中对应的value,得到用户登陆信息。最终也就实现了SSO
session默认30分钟过期,我们使用cookie+redis或者token两种方式,也可以通过一些手段,实现规定时间后过期功能
token
按照一定规则(编码和加密)生成字符串,字符串中包含用户登陆信息
将字符串通过cookie返回或者通过地址栏返回给后台
去其他模块的时候,就根据返回的字符串利用反规则解码出登陆信息实SSO

2、JWT

JWT(令牌加密)
令牌,token形式的单点登陆可以通过JWT来实现
自包含令牌:就是按照一定规则生成的字符串,包含所需信息
JWT:就是一种通用的规则,已经规定好了规则,使用jwt可以直接生成字符串,包含我们需要的信息
JWT生成字符串规则(包含3部分)
1、jwt头信息
2、有效荷载,包含主体信息(用户信息)
3、签名哈希,防伪标志

二、整合JWT

1、引入JWT依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

在这里插入图片描述

2、编写工具类

package com.yzpnb.common_utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
 * @author
 */
public class JwtUtils {
    
    public static final long EXPIRE = 1000 * 60 * 60 * 24;                      //设置token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";   //设置token密钥,我瞎写的,每个公司都有按自己规则生成的密钥
    
    //生成token字符串
    public static String getJwtToken(String id, String nickname){
        
        String JwtToken = Jwts.builder()                                           //构建jwt字符串
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")                             //设置jwt头信息
                
                .setSubject("guli-user")                                           //分类,名字随便起的,不同的分类可以设置不同的过期 
                .setIssuedAt(new Date())                                           //设置过期时间的计时起始值为当前时间 
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))      //设置过期时间为当前时间+EXPIRE我们设定的过期时间
                
                .claim("id", id)                                                //token主体,这里放你需要的信息,我们实现登陆,就放用户登陆信息
                .claim("nickname", nickname)                                    //需要多少主体信息,就设置多少个claim属性    
                
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)                    //签名哈希,根据指定规则和我们的密钥设定签名
                .compact();
        
        return JwtToken;
    }
        /**
         * 判断token是否存在与有效(方法一)
         * @param jwtToken token字符串
         * @return
         */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
        /**
         * 判断token是否存在与有效(方式二)
         * @param request 请求体
         * @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");                                           //根据请求体获取token字符串
        if(StringUtils.isEmpty(jwtToken)) return "";                                               //如果token为空,返回空串
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);  //解析token字符串
        Claims claims = claimsJws.getBody();                                                       //获取主体内容         
        return (String)claims.get("id");                                                           //获取主体值,这里只获取了id值 
    }
}


在这里插入图片描述

三、整合阿里云短信服务

1、阿里云短信服务开通

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、申请模板和签名

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、新建一个微服务引入依赖

<!--fastjson-->
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>fastjson</artifactId>
	    <version>1.2.70</version>
	</dependency>
<!--阿里云依赖-->
	<dependency>
	    <groupId>com.aliyun</groupId>
	    <artifactId>aliyun-java-sdk-core</artifactId>
	    <version>4.5.1</version>
	</dependency>

在这里插入图片描述

4、配置application.yml

server:
  port: 8005 #微服务端口

spring:
  application:
    name: service-msm #服务名
  profiles:
    active: dev #环境设置 dev表示构建阶段,test表示测试阶段,prod表示发布阶段
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #nacos
  redis: #redis配置
    host: 127.0.0.1 #你的redis地址
    port: 6379 #端口号
    database: 0
    timeout: 1800000
    lettuce:
      pool:
        max-active: 20
        max-wait: -1
        max-idle: 5 #最大阻塞等待时间(负数表示没限制)
        min-idle: 0

5、编写controller

注意:阿里云短信验证码,不会帮我们默认生成验证码,只能帮我们将短信发送,所以验证码需要自己生成
package com.yzpnb.msmservice.controller;

import com.yzpnb.common_utils.Result;
import com.yzpnb.msmservice.service.MsmService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/msmservice/")
@CrossOrigin
public class MsmController {

    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;//用来操作redis

    @ApiOperation("根据手机号发送短信")
    @GetMapping("shortMessage/{phoneNumber}")
    public Result shortMessage(@ApiParam(name = "phoneNumber",value = "电话号")
                               @PathVariable String phoneNumber){
        /**实现验证码5分钟内有效*/
        //1、从redis中获取验证码,获取到直接返回,获取不到重新生成发送
        String s = redisTemplate.opsForValue().get(phoneNumber);
        if(!StringUtils.isEmpty(s)){
            return Result.ok();
        }
        /**redis中没有内容,表示验证码过期,重新生成*/
        //1、生成验证码
        DecimalFormat fourdf=new DecimalFormat("0000");//生成4位模板
        String code = fourdf.format(new Random().nextInt(10000));//生成4位随机数字符串

        //2、将验证码发送
        Map<String,Object> map=new HashMap<>();
        map.put("code",code);//我习惯将验证码存储到Map集合中

        Boolean flag = msmService.shortMessage(phoneNumber, map);//传入手机号和验证码

        if (flag==true){
            //发送成功,把验证码放在redis中,并设置有效时间
            //key用手机号,value是验证码,5表示,TimeUnit.MINUTES表示分钟,配合上数字5表示5分钟
            redisTemplate.opsForValue().set(phoneNumber,code,5, TimeUnit.MINUTES);
            return Result.ok();
        }else{
            return Result.error().message("发送失败");
        }
    }
}

在这里插入图片描述

6、编写service

package com.yzpnb.msmservice.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.yzpnb.msmservice.service.MsmService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Map;

@Service
public class MsmServiceImpl implements MsmService {
    /**
     * 根据手机号发送短信
     * @param phoneNumber
     * @param map
     * @return
     */
    @Override
    public Boolean shortMessage(String phoneNumber, Map<String, Object> map) {
        if(StringUtils.isEmpty(phoneNumber)) return false;//如果手机号为空,返回false

        /**
         * 生成默认链接,第一个参数域节点,用默认即可
         * 第二个参数是阿里云id
         * 第三个参数是阿里云密钥
         */
        DefaultProfile profile=DefaultProfile.getProfile("default",
                                            "你的密钥id",
                                                "你的密钥");
        IAcsClient acsClient = 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", phoneNumber);//设置发送手机号
        request.putQueryParameter("SignName", "我的农大线上学院网站");//你申请的签名的名称
        request.putQueryParameter("TemplateCode", "你的模板CODE");//你的模板CODE
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(map));//验证码,需要用json格式,所以使用map,可以直接转换为json

        //发送
        boolean b=false;//默认为不成功
        try {
            CommonResponse commonResponse =acsClient.getCommonResponse(request);//发送短信

            b=commonResponse.getHttpResponse().isSuccess();//返回是否发送成功
        } catch (ClientException e) {
            e.printStackTrace();
        }

        return b;
    }
}

在这里插入图片描述

7、测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、登陆接口(后端)

1、环境搭建

1、数据库源码

在这里插入图片描述

# Host: 47.93.118.241:33306  (Version 5.7.21)
# Date: 2019-11-15 11:58:11
# Generator: MySQL-Front 6.1  (Build 1.26)


#
# Structure for table "ucenter_member"
#

CREATE TABLE `ucenter_member` (
  `id` char(19) NOT NULL COMMENT '会员id',
  `openid` varchar(128) DEFAULT NULL COMMENT '微信openid',
  `mobile` varchar(11) DEFAULT '' COMMENT '手机号',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
  `sex` tinyint(2) unsigned DEFAULT NULL COMMENT '性别 1 女,2 男',
  `age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
  `avatar` varchar(255) DEFAULT NULL COMMENT '用户头像',
  `sign` varchar(100) DEFAULT NULL COMMENT '用户签名',
  `is_disabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否禁用 1(true)已禁用,  0(false)未禁用',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表';

#
# Data for table "ucenter_member"
#

INSERT INTO `ucenter_member` VALUES ('1',NULL,'13700000001','96e79218965eb72c92a549dd5a330112','小三123',1,5,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-01 12:11:33','2019-11-08 11:56:01'),('1080736474267144193',NULL,'13700000011','96e79218965eb72c92a549dd5a330112','用户XJtDfaYeKk',1,19,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-02 12:12:45','2019-01-02 12:12:56'),('1080736474355224577',NULL,'13700000002','96e79218965eb72c92a549dd5a330112','用户wUrNkzAPrc',1,27,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-02 12:13:56','2019-01-02 12:14:07'),('1086387099449442306',NULL,'13520191388','96e79218965eb72c92a549dd5a330112','用户XTMUeHDAoj',2,20,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099520745473',NULL,'13520191389','96e79218965eb72c92a549dd5a330112','用户vSdKeDlimn',1,21,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099608825858',NULL,'13520191381','96e79218965eb72c92a549dd5a330112','用户EoyWUVXQoP',1,18,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099701100545',NULL,'13520191382','96e79218965eb72c92a549dd5a330112','用户LcAYbxLNdN',2,24,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099776598018',NULL,'13520191383','96e79218965eb72c92a549dd5a330112','用户dZdjcgltnk',2,25,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099852095490',NULL,'13520191384','96e79218965eb72c92a549dd5a330112','用户wNHGHlxUwX',2,23,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1106746895272849410','o1R-t5u2TfEVeVjO9CPGdHPNw-to',NULL,NULL,'檀梵\'',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/zZfLXcetf2Rpsibq6HbPUWKgWSJHtha9y1XBeaqluPUs6BYicW1FJaVqj7U3ozHd3iaodGKJOvY2PvqYTuCKwpyfQ/132',NULL,0,0,'2019-03-16 10:39:57','2019-03-16 10:39:57'),('1106822699956654081',NULL,NULL,NULL,NULL,NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-03-16 15:41:10','2019-03-16 15:41:10'),('1106823035660357634','o1R-t5i4gENwHYRb5lVFy98Z0bdk',NULL,NULL,'GaoSir',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJI53RcCuc1no02os6ZrattWGiazlPnicoZQ59zkS7phNdLEWUPDk8fzoxibAnXV1Sbx0trqXEsGhXPw/132',NULL,0,0,'2019-03-16 15:42:30','2019-03-16 15:42:30'),('1106823041599492098',NULL,NULL,NULL,NULL,NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-03-16 15:42:32','2019-03-16 15:42:32'),('1106823115788341250','o1R-t5l_3rnbZbn4jWwFdy6Gk6cg',NULL,NULL,'换个网名哇、',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/jJHyeM0EN2jhB70LntI3k8fEKe7W6CwykrKMgDJM4VZqCpcxibVibX397p0vmbKURGkLS4jxjGB0GpZfxCicgt07w/132',NULL,0,0,'2019-03-16 15:42:49','2019-03-16 15:42:49'),('1106826046730227714','o1R-t5gyxumyBqt0CWcnh0S6Ya1g',NULL,NULL,'我是Helen',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKDRfib8wy7A2ltERKh4VygxdjVC1x5OaOb1t9hot4JNt5agwaVLdJLcD9vJCNcxkvQnlvLYIPfrZw/132',NULL,0,0,'2019-03-16 15:54:28','2019-03-16 15:54:28'),('1106828185829490690','o1R-t5nNlou5lRwBVgGNJFm4rbc4',NULL,NULL,' 虎头',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKxCqRzuYWQmpwiaqQEjNxbC7WicebicXQusU306jgmfoOzUcFg1qaDq5BStiblwBjw5dUOblQ2gUicQOQ/132',NULL,0,0,'2019-03-16 16:02:58','2019-03-16 16:02:58'),('1106830599651442689','o1R-t5hZHQB1cbX7HZJsiM727_SA',NULL,NULL,'是吴啊',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJ9CsqApybcs7f3Dyib9IxIh0sBqJb7LicbjU4WticJFF0PVwFvHgtbFdBwfmk3H2t3NyqmEmVx17tRA/132',NULL,0,0,'2019-03-16 16:12:34','2019-03-16 16:12:34'),('1106830976199278593','o1R-t5meKOoyEJ3-IhWRCBKFcvzU',NULL,NULL,'我才是Helen',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epMicP9UT6mVjYWdno0OJZkOXiajG0sllJTbGJ9DYiceej2XvbDSGCK8LCF7jv1PuG2uoYlePWic9XO8A/132',NULL,0,0,'2019-03-16 16:14:03','2019-03-16 16:14:03'),('1106831936900415490','o1R-t5jXYSWakGtnUBnKbfVT5Iok',NULL,NULL,'文若姬',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/3HEmJwpSzguqqAyzmBwqT6aicIanswZibEOicQInQJI3ZY1qmu59icJC6N7SahKqWYv24GvX5KH2fibwt0mPWcTJ3fg/132',NULL,0,0,'2019-03-16 16:17:52','2019-03-16 16:17:52'),('1106832491064442882','o1R-t5sud081Qsa2Vb2xSKgGnf_g',NULL,NULL,'Peanut',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-03-16 16:20:04','2019-03-16 16:20:04'),('1106833021442510849','o1R-t5lsGc3I8P5bDpHj7m_AIRvQ',NULL,NULL,'食物链终结者',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/MQ7qUmCprK9am16M1Ia1Cs3RK0qiarRrl9y8gsssBjIZeS2GwKSrnq7ZYhmrzuzDwBxSMMAofrXeLic9IBlW4M3Q/132',NULL,0,0,'2019-03-16 16:22:11','2019-03-16 16:22:11'),('1191600824445046786',NULL,'15210078344','96e79218965eb72c92a549dd5a330112','IT妖姬',1,5,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-11-05 14:19:10','2019-11-08 18:04:43'),('1191616288114163713',NULL,'17866603606','96e79218965eb72c92a549dd5a330112','xiaowu',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-11-05 15:20:37','2019-11-05 15:20:37'),('1195187659054329857',NULL,'15010546384','96e79218965eb72c92a549dd5a330112','qy',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-11-15 11:51:58','2019-11-15 11:51:58');

2、创建子模块

在这里插入图片描述

3、application.yml

server:
  port: 8006 #微服务端口号为8006
spring:
  application:
    name: service-ucenter #服务名
  profiles:
    active: dev #环境设置 dev表示构建阶段,test表示测试阶段,prod表示发布阶段
  datasource: #数据源
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/gulischool?serverTimeZone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
  jackson: #我们的时区是东八区,应该加8个小时,时区显示格式也需要改成我们想要的
    date-format: yyyy-MM-DD HH:mm:ss
    time-zone: GMT+8
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #nacos
feign:
  client:
    config:
      default:
        connect-timeout: 10000 #设置超时限制,必须超过10000ms才报错
        read-timeout: 10000 #设置Feign服务熔断机制的最大超时限制
  hystrix:
    enabled: true #开启熔断机制
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 #设置hystri超时时间 默认1000ms(10s)


mybatis-plus:
  mapper-locations: classpath:com/yzpnb/eduservice/mapper/xml/*.xml #配置mapper xml文件的路径

4、使用代码生成器,生成表对应MVC层次结构(记住将参数改成你自己的)

package gennerator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
 * @author
 * @since 2018/12/13
 */
public class CodeGenerator {

    @Test
    public void run() {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir("D:\\IdeaProjects\\gulischool\\service\\service-ucenter" + "/src/main/java"); //输出目录,生成的代码最终输出的地方,请写你的项目的绝对路径
        gc.setAuthor("testjava");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖
        gc.setServiceName("%sService");	//去掉Service接口的首字母I
        gc.setIdType(IdType.ID_WORKER_STR); //主键策略,ID_WORKER表示默认Integer,ID_WORKER_STR表示字符型
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/gulischool?serverTimeZone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置(生成的包名)
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.yzpnb");
        pc.setModuleName("ucenter_service"); //模块名 com.yzpnb.eduservice

        pc.setController("controller"); //com.yzpnb.eduservice.controller
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("ucenter_member");//你要为哪个表生成代码
        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
        strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);


        // 6、执行
        mpg.execute();
    }
}


在这里插入图片描述

5、给实体类加自动填充,给controller层加跨域

在这里插入图片描述

6、启动类

package com.yzpnb.ucenter_service;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.yzpnb")
@MapperScan("com.yzpnb.ucenter_service.mapper")
public class UcenterApplication {
    public static void main(String[] args) {
        SpringApplication.run(UcenterApplication.class,args);
    }
}

2、编写MD5加密工具类

MD5
目前最常见的一种密码加密形式
此加密的最大特点就是,只能加密,无法解密
我们想要判断输入的密码是否等于数据库中存储的加密后的密码
必须使用你设定的相同的MD5规则加密后,与数据库中数据比较
为什么无法解密
很简单,加密的时候采用位运算,并在每个加密后的字符后面加上,加密前字符的全新加密格式
位运算后的数据,由于进1,补零,原来的值就不见了,无法通过反运算获取原值
所以就是把加密规则告诉你,你也无法反加密出原数据,因为你根本不知道,加密后消掉了几个1,补了多少个零
package com.yzpnb.common_utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {

    public static String encrypt(String strSrc){
        char hexChars[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

        byte[] bytes=strSrc.getBytes();//将字符串转换成字节数组
        try {

            MessageDigest md =MessageDigest.getInstance("MD5");//通过反射获取MD5加密Digest对象
            md.update(bytes);                                   //将字节数组传入

			bytes=md.digest();                                  //将字节数组改为加密后的字节数组
            int j=bytes.length;                                 //获取字节数组的长度
            char[] chars=new char[j*2];                         //新建一个字符数组,长度为字节数组长度的二倍
            int k=0;                                            //定义一个变量,用来指定字符数组的下标
            for(int i=0;i<bytes.length;i++){                    //循环字节数组
                byte b=bytes[i];                                //依次获取字节数组中的值
                chars[k++]=hexChars[b>>> 4 & 0xf];              //将字节右位移4次 然后对十六进制f进行位与运算加密
                chars[k++]=hexChars[b & 0xf];                   //在每个字符的后面,跟上字节对十六进制f的位与运算加密值
            }
            return new String(chars);                           //最后将加密好的字符数组转换为字符串,返回
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错!!+"+e);
        }

    }
}

在这里插入图片描述

3、编写controller接口

package com.yzpnb.ucenter_service.controller;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yzpnb.common_utils.Result;
import com.yzpnb.ucenter_service.entity.UcenterMember;
import com.yzpnb.ucenter_service.service.UcenterMemberService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 会员表 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2020-06-03
 */
@RestController
@RequestMapping("/ucenter_service/ucenter-member")
@CrossOrigin
public class UcenterMemberController {

    @Autowired
    private UcenterMemberService ucenterMemberService;

    @ApiOperation("登录,判断登录的手机号和密码是否正确,返回JWT加密后token字符串")
    @PostMapping("login")
    public Result login(@RequestBody UcenterMember ucenterMember){

        String token=ucenterMemberService.login(ucenterMember);//返回由JWT生成的token字符串
        return Result.ok().data("token",token);
    }
}


在这里插入图片描述

4、service接口

package com.yzpnb.ucenter_service.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yzpnb.common_utils.JwtUtils;
import com.yzpnb.common_utils.MD5;
import com.yzpnb.service_base_handler.CustomExceptionHandler;
import com.yzpnb.ucenter_service.entity.UcenterMember;
import com.yzpnb.ucenter_service.mapper.UcenterMemberMapper;
import com.yzpnb.ucenter_service.service.UcenterMemberService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * <p>
 * 会员表 服务实现类
 * </p>
 *
 * @author testjava
 * @since 2020-06-03
 */
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {

    /**
     * 登录,判断登录的手机号和密码是否正确,返回JWT加密后token字符串
     * @param ucenterMember
     * @return
     */
    @Override
    public String login(UcenterMember ucenterMember) {
        /**判断手机号和密码是否为空,为空直接终止程序,返回自定义异常信息*/
        if(StringUtils.isEmpty(ucenterMember.getMobile()) || StringUtils.isEmpty(ucenterMember.getPassword())){
            throw new CustomExceptionHandler(20001,"手机号或密码为空");
        }

        /**判断手机号和密码是否正确,账号是否可用,有一项出错,直接返回自定义异常**/
        //根据手机号查询数据,有值表示手机号正确,没值直接失败
        QueryWrapper<UcenterMember> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("mobile",ucenterMember.getMobile());
        UcenterMember one = baseMapper.selectOne(queryWrapper);

        if(one !=null){//有值表示手机号正确
            //通过MD5加密用户输入的密码,比较是否与数据库中密码相同
            String password= MD5.encrypt(ucenterMember.getPassword());//根据工具类获取加密后密码
            if(!one.getPassword().equals(password)){//判断密码是否正确,不正确返回false,通过非运算获取到true,报异常
                throw new CustomExceptionHandler(20001,"密码错误");
            }
            //判断当前是否禁用状态
            if(one.getIsDisabled()){//如果isDisable为真值,表示现在此账号是禁用状态
                throw new CustomExceptionHandler(20001,"此账号现在被禁用,可能有其他人登录");
            }

        }else{//没有值表示手机号没有注册
            throw new CustomExceptionHandler(20001,"此手机号没有注册");
        }

        /**走到这说明信息全部正确,使用JWT根据id和昵称生成token字符串并返回**/
        String token = JwtUtils.getJwtToken(one.getId(), one.getNickname());//传入我们查出来的对象的值,形参中,只有电话号和密码

        return token;
    }
}

在这里插入图片描述

5、测试

在这里插入图片描述
在这里插入图片描述

五、注册接口(后端,需要spring cloud的feign组件,跨服务调用阿里云短信微服务)

1、创建实体类,包含注册信息和验证码

package com.yzpnb.ucenter_service.entity.vo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.stereotype.Component;

@Component
@Data
public class RegisterVo {

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "昵称")
    private String nickname;
    
    @ApiModelProperty(value = "验证码")
    private String code;
}

在这里插入图片描述

2、controller

@ApiOperation("注册,判断注册手机号是否已经存在,密码需要加密存储,需要验证码正确才能注册成功")
    @PostMapping("register")
    public Result register(@ApiParam(name = "registerVo",value = "注册对象")
                           @RequestBody RegisterVo registerVo){
        ucenterMemberService.register(registerVo);
        return Result.ok();
    }

3、service

/**
     * 注册,判断注册手机号是否已经存在,密码需要加密存储,需要验证码正确才能注册成功
     * @param registerVo
     */
    @Autowired
    RedisTemplate<String,String> redisTemplate=new RedisTemplate<>();//创建redisTemplate对象,使用redis数据库
    @Override
    public void register(RegisterVo registerVo) {
        /**获取数据**/
        String mobile=registerVo.getMobile();     //获取手机号
        String password=registerVo.getPassword(); //获取密码
        String nickname=registerVo.getNickname(); //获取昵称
        String code=registerVo.getCode();         //获取验证码

        /**判断手机号和密码是否为空,为空直接终止程序,返回自定义异常信息,不为空,判断手机号是否重复*/
        if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)){
            throw new CustomExceptionHandler(20001,"手机号或密码为空");
        }else{
            //不为空则判断手机号是否重复
            QueryWrapper<UcenterMember> queryWrapper=new QueryWrapper<>();
            queryWrapper.eq("mobile",mobile);
            int count = baseMapper.selectCount(queryWrapper);//查询是否有相同电话号,返回符合匹配条件的数据条数

            if(count>0){//如果满足条件的个数>0表示已经有此电话号,抛异常
                throw new CustomExceptionHandler(20001,"此手机号已经被注册");
            }
        }

        /**判断昵称是否为空,不为空判断是否重复,如果昵称为空,或者昵称重复,报自定义异常**/
        if(StringUtils.isEmpty(nickname)){
            throw new CustomExceptionHandler(20001,"请输入昵称");
        }else{
            //不为空判断是否昵称重复
            QueryWrapper<UcenterMember> queryWrapper=new QueryWrapper<>();
            queryWrapper.eq("nickname",nickname);
            int count = baseMapper.selectCount(queryWrapper);//查询是否有相同电话号,返回符合匹配条件的数据条数

            if(count>0){//如果满足条件的个数>0表示已经有此昵称,抛异常
                throw new CustomExceptionHandler(20001,"昵称重复");
            }

        }

        /**判断当前手机号的验证码是否已经过期,或者没有生成**/
        if(redisTemplate.hasKey(mobile)==false) throw new CustomExceptionHandler(20001,"此手机号的验证码没有生成或已过期");

        /**确保发送过验证码以后,判断验证码是否为空,不为空则判断验证码是否正确,为空,报自定义异常**/
        if(StringUtils.isEmpty(code)){
            throw new CustomExceptionHandler(20001,"请输入验证码");
        }else{
            //判断验证码是否与redis中存储的key为当前手机号的value值相等
            String value = redisTemplate.opsForValue().get(mobile);//获取key为当前手机号的value值
            if(!value.equals(code)){
                throw new CustomExceptionHandler(20001,"验证码错误");
            }
        }

        /**走到这说明没问题,将密码加密,存入数据库**/
        //一切信息正确且不重复,加密密码
        password=MD5.encrypt(password);

        UcenterMember ucenterMember=new UcenterMember();//创建用户对象
        ucenterMember.setMobile(mobile);
        ucenterMember.setPassword(password);
        ucenterMember.setNickname(nickname);
        ucenterMember.setIsDisabled(false);//用户不禁用
        //设置默认头像
        ucenterMember.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132");

        baseMapper.insert(ucenterMember);//添加数据
    }

4、测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

六、根据token获取用户信息

1、controller

@ApiOperation("根据token获取用户信息")
    @GetMapping("getUserInfo")
    public Result getUserInfo(HttpServletRequest request){//使用原生HttpServlet请求对象,获取请求体

        //通过JWT工具类方法,根据request请求头,返回用户id
        String id = JwtUtils.getMemberIdByJwtToken(request);
        //根据id查询数据
        UcenterMember ucenterMember = ucenterMemberService.getById(id);
        return Result.ok().data("userInfo",ucenterMember);
    }

七、登陆与注册(前端,需要element ui,js-cookie,vue-qriously)

使用element ui
import ElementUI from 'element-ui' //element-ui的全部组件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI

在这里插入图片描述

1、api接口,配置nginx

在这里插入图片描述
在这里插入图片描述

3、布局页面

<template>
  <div class="sign">
      <!--标题-->
      <div class="logo">
        <img src="~/assets/img/logo.png" alt="logo">
      </div>
      <!--表单-->
      <nuxt/>
    </div>
</template>

在这里插入图片描述

2、静态页面(代码全在GitHub中)

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

3、代码实现

1、注册(源代码到github中取)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、登陆(因为我们登陆需要使用cookie,所以需要下载js-cookie插件)

1、下载js-cookie插件:npm install js-cookie

在这里插入图片描述
在这里插入图片描述

4、将token存入cookie,请求登陆接口,获取用户信息,然后将用户信息,存入cookie中
注意这是第四步哦!但为我好理解,将这步放前面
{domain:localhost}:表示只要当前访问的是localhost,cookie都会传递,你可以改成ip地址

在这里插入图片描述

2、创建前端拦截器,拦截request请求,查看cookie中是否有token字符串,有就将字符串放到header(请求头中)

在这里插入图片描述
在这里插入图片描述

import axios from 'axios'
import cookie from 'js-cookie'
import { MessageBox, Message } from 'element-ui'
// 创建axios实例
const service = axios.create({
  baseURL: 'http://localhost:9001', // api的base_url
  timeout: 20000 // 请求超时时间
})

// http request 拦截器
service.interceptors.request.use(
  config => {
  //debugger
  if (cookie.get('token')) {
    config.headers['token'] = cookie.get('token');
  }
    return config
  },
  err => {
  return Promise.reject(err);
})
// http response 拦截器
service.interceptors.response.use(
  response => {
    //debugger
    if (response.data.code == 28004) {
        console.log("response.data.resultCode是28004")
        // 返回 错误代码-1 清除ticket信息并跳转到登录页面
        //debugger
        window.location.href="/login"
        return
    }else{
      if (response.data.code !== 20000) {
        //25000:订单支付中,不做任何提示
        if(response.data.code != 25000) {
          Message({
            message: response.data.message || 'error',
            type: 'error',
            duration: 5 * 1000
          })
        }
      } else {
        return response;
      }
    }
  },
  error => {
    return Promise.reject(error.response)   // 返回接口返回的错误信息
});
export default service

3、根据token值调用接口,获取用户信息,将返回的信息存入cookie

在这里插入图片描述
在这里插入图片描述

5、首页面显示coolie中的用户信息

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

八、OAuth2.0

1、OAuth2

针对特定问题的解决方案
1、开放系统间授权问题
2、分布式访问问题
开放系统间授权问题
举例:相片拥有者,将照片存储到云中,这时负责打印的,是没有访问云中照片的授权的,所以无法打印
而相片拥有者,这时通过授权,让打印机可以打印照片

在这里插入图片描述

方式1、适用于同一公司内部的多个系统,不适用于不受信的第三方应用

在这里插入图片描述

方式2、适用于合作商或者授信的不同业务部门之间

在这里插入图片描述

方式3、接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议

在这里插入图片描述

分布式访问问题,解决微服务安全

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

去哪里学习?《OAuth2最简向导》
川崎高彦:OAuth2领域专家,开发了一个OAuth2 sass服务,OAuth2 as Service,并且做成了一个公司
再融资的过程中为了向投资人解释OAuth2是什么,于是写了一篇文章,《OAuth2最简向导》

2、OAuth2.0实践了解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、OAuth2的误解

在这里插入图片描述
在这里插入图片描述

九、微信扫码登录(难度较高,选择性观看)

1、申请资质

1、开发者资质(看看就好,想要注册你需要有一个公司)

注册网址https://open.weixin.qq.com

在这里插入图片描述
在这里插入图片描述

2、申请网站应用名称(二维码下方显示你的应用名),网站域名地址(扫完码,会跳转这个域名)(看看就好)

尚硅谷提供的
wx:
	open:
		# 微信开放平台 appid
		app_id: wxed9954c01bb89b47
		# 微信开放平台appsecret
		app_secret: a7482517235173ddb4083788de60b90e
		# 微信开放平台 重定向url(guli.shop需要在微信开放平台配置)
		redirect_url: http://guli.shop/api/ucenter/wx/callback

3、官方文档

https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN

在这里插入图片描述

2、配置application.yml

wx:
  open:
    # 微信开放平台 appid
    app_id: wxed9954c01bb89b47
    # 微信开放平台appsecret
    app_secret: a7482517235173ddb4083788de60b90e
    # 微信开放平台 重定向url(guli.shop需要在微信开放平台配置)
    redirect_url: http://guli.shop/api/ucenter/wx/callback

在这里插入图片描述

3、配置类获取配置文件中的值

package com.yzpnb.ucenter_service.util;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ConstantYmlUtil implements InitializingBean {
    @Value("${wx.open.app_id}")
    private String appId;
    @Value("${wx.open.app_secret}")
    private String appSecret;
    @Value("${wx.open.redirect_url}")
    private String redirectUrl;
    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;
    @Override
    public void afterPropertiesSet() throws Exception {
        WX_OPEN_APP_ID = appId;
        WX_OPEN_APP_SECRET = appSecret;
        WX_OPEN_REDIRECT_URL = redirectUrl;
    }
}

在这里插入图片描述

4、生成二维码

在这里插入图片描述

package com.yzpnb.ucenter_service.controller.api;

import com.yzpnb.service_base_handler.CustomExceptionHandler;
import com.yzpnb.ucenter_service.util.ConstantYmlUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

@CrossOrigin
@Controller//注意这里没有配置 @RestController,如果你使用了此注解,此注解会将字符串返回,而不能重定向了,所以我们这个类不能用此注解
@RequestMapping("/ucenter_service/api/wx")
public class WxApiController {

    //生成微信二维码
    @GetMapping("login")
    public String genQrConnect(HttpSession session) {
        // 微信开放平台授权baseUrl
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
        "?appid=%s" +               //这里通过%s占位,之后我们可以通过String.format统一赋值,不容易出错,下面的内容都是
        "&redirect_uri=%s" +
        "&response_type=code" +
        "&scope=snsapi_login" +
        "&state=%s" +
        "#wechat_redirect";

        // 回调地址,redirectUrl,官方规定,我们需要对这个字符串进行URLEncoder编码
        String redirectUrl = ConstantYmlUtil.WX_OPEN_REDIRECT_URL; //获取业务服务器重定向地址
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
        } catch (UnsupportedEncodingException e) {
            throw new CustomExceptionHandler(20001, e.getMessage());
        }

        // 防止csrf攻击(跨站请求伪造攻击)
        //String state = UUID.randomUUID().toString().replaceAll("-", "");//一般情况下会使用一个随机数
        String state = "imhelen";//为了让大家能够使用我搭建的外网的微信回调跳转服务器,这里填写你在ngrok的前置域名
        System.out.println("state = " + state);
        // 采用redis等进行缓存state 使用sessionId为key 30分钟后过期,可配置
        //键:"wechar-open-state-" + httpServletRequest.getSession().getId()
        //值:satte
        //过期时间:30分钟
        //生成qrcodeUrl
        String qrcodeUrl = String.format(//为baseUrl中的占位符传值
                baseUrl,
                ConstantYmlUtil.WX_OPEN_APP_ID,
                redirectUrl,
                state);

        return "redirect:" + qrcodeUrl;//重定向到地址,如果你使用了@RestController注解,此注解会将字符串返回,而不能重定向了,所以我们这个类不能用此注解
    }
}

在这里插入图片描述

5、测试二维码

在这里插入图片描述
在这里插入图片描述

6、通过一些小手段,让我们扫描微信二维码后,跳转到我们本地(只是给我们这些没有公司的人用,比如和我一样还在上学的人,只能蹭人家的二维码)

尚硅谷的重定向接口地址:http://guli.shop/api/ucenter/wx/callback
它封装里一些重定向代码,当我们扫描二维码时,会重定向到
localhost:8150/api/ucenter/wx/callback?code=***** (省略内容) **** &state=*******
1、把我们自己的端口号改为8150和重定向的端口一致

在这里插入图片描述

2、将我们的请求地址和重定向一致,改为api/ucenter/wx

在这里插入图片描述

3、将我们的方法起名为和重定向地址相同的callback(代码还没写完,不需要照着写)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7、创建HTTPClient工具类(HttpClient是sun公司java原生技术,可以不用浏览器模拟http请求)

1、引入依赖

<!--httpClient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.12</version>
        </dependency>
        <!--commons-io-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.7</version>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>

在这里插入图片描述

2、工具类

package com.yzpnb.ucenter_service.util;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 *  依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar
 * @author zhaoyb
 *
 */
public class HttpClientUtils {

	public static final int connTimeout=10000;
	public static final int readTimeout=10000;
	public static final String charset="UTF-8";
	private static HttpClient client = null;

	static {
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
		cm.setMaxTotal(128);
		cm.setDefaultMaxPerRoute(128);
		client = HttpClients.custom().setConnectionManager(cm).build();
	}

	public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
		return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
	}

	public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
		return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
	}

	public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {
		return postForm(url, params, null, connTimeout, readTimeout);
	}

	public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {
		return postForm(url, params, null, connTimeout, readTimeout);
	}

	public static String get(String url) throws Exception {
		return get(url, charset, null, null);
	}

	public static String get(String url, String charset) throws Exception {
		return get(url, charset, connTimeout, readTimeout);
	}

	/**
	 * 发送一个 Post 请求, 使用指定的字符集编码.
	 *
	 * @param url
	 * @param body RequestBody
	 * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
	 * @param charset 编码
	 * @param connTimeout 建立链接超时时间,毫秒.
	 * @param readTimeout 响应超时时间,毫秒.
	 * @return ResponseBody, 使用指定的字符集编码.
	 * @throws ConnectTimeoutException 建立链接超时异常
	 * @throws SocketTimeoutException  响应超时
	 * @throws Exception
	 */
	public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
			throws ConnectTimeoutException, SocketTimeoutException, Exception {
		HttpClient client = null;
		HttpPost post = new HttpPost(url);
		String result = "";
		try {
			if (StringUtils.isNotBlank(body)) {
				HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
				post.setEntity(entity);
			}
			// 设置参数
			Builder customReqConf = RequestConfig.custom();
			if (connTimeout != null) {
				customReqConf.setConnectTimeout(connTimeout);
			}
			if (readTimeout != null) {
				customReqConf.setSocketTimeout(readTimeout);
			}
			post.setConfig(customReqConf.build());

			HttpResponse res;
			if (url.startsWith("https")) {
				// 执行 Https 请求.
				client = createSSLInsecureClient();
				res = client.execute(post);
			} else {
				// 执行 Http 请求.
				client = HttpClientUtils.client;
				res = client.execute(post);
			}
			result = IOUtils.toString(res.getEntity().getContent(), charset);
		} finally {
			post.releaseConnection();
			if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
				((CloseableHttpClient) client).close();
			}
		}
		return result;
	}


	/**
	 * 提交form表单
	 *
	 * @param url
	 * @param params
	 * @param connTimeout
	 * @param readTimeout
	 * @return
	 * @throws ConnectTimeoutException
	 * @throws SocketTimeoutException
	 * @throws Exception
	 */
	public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {

		HttpClient client = null;
		HttpPost post = new HttpPost(url);
		try {
			if (params != null && !params.isEmpty()) {
				List<NameValuePair> formParams = new ArrayList<NameValuePair>();
				Set<Entry<String, String>> entrySet = params.entrySet();
				for (Entry<String, String> entry : entrySet) {
					formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
				}
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
				post.setEntity(entity);
			}

			if (headers != null && !headers.isEmpty()) {
				for (Entry<String, String> entry : headers.entrySet()) {
					post.addHeader(entry.getKey(), entry.getValue());
				}
			}
			// 设置参数
			Builder customReqConf = RequestConfig.custom();
			if (connTimeout != null) {
				customReqConf.setConnectTimeout(connTimeout);
			}
			if (readTimeout != null) {
				customReqConf.setSocketTimeout(readTimeout);
			}
			post.setConfig(customReqConf.build());
			HttpResponse res = null;
			if (url.startsWith("https")) {
				// 执行 Https 请求.
				client = createSSLInsecureClient();
				res = client.execute(post);
			} else {
				// 执行 Http 请求.
				client = HttpClientUtils.client;
				res = client.execute(post);
			}
			return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
		} finally {
			post.releaseConnection();
			if (url.startsWith("https") && client != null
					&& client instanceof CloseableHttpClient) {
				((CloseableHttpClient) client).close();
			}
		}
	}




	/**
	 * 发送一个 GET 请求
	 *
	 * @param url
	 * @param charset
	 * @param connTimeout  建立链接超时时间,毫秒.
	 * @param readTimeout  响应超时时间,毫秒.
	 * @return
	 * @throws ConnectTimeoutException   建立链接超时
	 * @throws SocketTimeoutException   响应超时
	 * @throws Exception
	 */
	public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
			throws ConnectTimeoutException,SocketTimeoutException, Exception {

		HttpClient client = null;
		HttpGet get = new HttpGet(url);
		String result = "";
		try {
			// 设置参数
			Builder customReqConf = RequestConfig.custom();
			if (connTimeout != null) {
				customReqConf.setConnectTimeout(connTimeout);
			}
			if (readTimeout != null) {
				customReqConf.setSocketTimeout(readTimeout);
			}
			get.setConfig(customReqConf.build());

			HttpResponse res = null;

			if (url.startsWith("https")) {
				// 执行 Https 请求.
				client = createSSLInsecureClient();
				res = client.execute(get);
			} else {
				// 执行 Http 请求.
				client = HttpClientUtils.client;
				res = client.execute(get);
			}

			result = IOUtils.toString(res.getEntity().getContent(), charset);
		} finally {
			get.releaseConnection();
			if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
				((CloseableHttpClient) client).close();
			}
		}
		return result;
	}


	/**
	 * 从 response 里获取 charset
	 *
	 * @param ressponse
	 * @return
	 */
	@SuppressWarnings("unused")
	private static String getCharsetFromResponse(HttpResponse ressponse) {
		// Content-Type:text/html; charset=GBK
		if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
			String contentType = ressponse.getEntity().getContentType().getValue();
			if (contentType.contains("charset=")) {
				return contentType.substring(contentType.indexOf("charset=") + 8);
			}
		}
		return null;
	}



	/**
	 * 创建 SSL连接
	 * @return
	 * @throws GeneralSecurityException
	 */
	private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
		try {
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
				public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
					return true;
				}
			}).build();

			SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

				@Override
				public boolean verify(String arg0, SSLSession arg1) {
					return true;
				}

				@Override
				public void verify(String host, SSLSocket ssl)
						throws IOException {
				}

				@Override
				public void verify(String host, X509Certificate cert)
						throws SSLException {
				}

				@Override
				public void verify(String host, String[] cns,
								   String[] subjectAlts) throws SSLException {
				}

			});

			return HttpClients.custom().setSSLSocketFactory(sslsf).build();

		} catch (GeneralSecurityException e) {
			throw e;
		}
	}

	public static void main(String[] args) {
		try {
			String str= post("https://localhost:443/ssl/test.shtml","name=12&page=34","application/x-www-form-urlencoded", "UTF-8", 10000, 10000);
			//String str= get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");
            /*Map<String,String> map = new HashMap<String,String>();
            map.put("name", "111");
            map.put("page", "222");
            String str= postForm("https://localhost:443/ssl/test.shtml",map,null, 10000, 10000);*/
			System.out.println(str);
		} catch (ConnectTimeoutException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SocketTimeoutException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

在这里插入图片描述

8、controller层接口

@Autowired
    UcenterMemberService ucenterMemberService;

    @ApiOperation("获取扫描人信息,添加数据")
    @GetMapping("callback")
    public String callback(@ApiParam(name = "code",value = "code是微信官方api生成的唯一验证码(有过期时间),这个值可以通过请求固定地址" +
                                                           "获取访问凭证(access_token)和当前微信的唯一id值(openid)" +
                                                           "获取的凭证和id值可以让我们获取到微信扫码人的信息(头像昵称等)")String code,
                           @ApiParam(name = "state",value = "state是生成二维码的时候原样传递过来的") String state){//获取code和state

        /**1、通过获取的code值,请求固定地址获取访问凭证和微信id**/
        //1、向认证服务器发送请求换取access_token
        String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
        "?appid=%s" +
        "&secret=%s" +
        "&code=%s" +
        "&grant_type=authorization_code";//占位形式拼接固定地址
        String accessTokenUrl = String.format(baseAccessTokenUrl,
                ConstantYmlUtil.WX_OPEN_APP_ID,
                ConstantYmlUtil.WX_OPEN_APP_SECRET,
                code);

        String result = null;
        try {
            result = HttpClientUtils.get(accessTokenUrl);//根据http请求获取微信响应结果
            System.out.println("accessToken=============" + result);
        } catch (Exception e) {
            throw new CustomExceptionHandler(20001, "获取access_token失败");
        }
        //解析json字符串
        Gson gson = new Gson();
        HashMap map = gson.fromJson(result, HashMap.class);
        String accessToken = (String)map.get("access_token");//获取访问凭证
        String openid = (String)map.get("openid");           //获取微信id

         /**2、根据微信id获取微信的用户信息**/
        //查询数据库当前用用户是否曾经使用过微信登录
        QueryWrapper<UcenterMember> queryWrapper =new QueryWrapper<>();
        queryWrapper.eq("openid",openid);
        UcenterMember ucenterMember = ucenterMemberService.getOne(queryWrapper);
        if(ucenterMember == null){//没有值表示没用过,需要获取信息并注册
            System.out.println("新用户注册");
            //访问微信的资源服务器,获取用户信息
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
            "?access_token=%s" +
            "&openid=%s";
            String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openid);
            String resultUserInfo = null;
            try {
                resultUserInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println("resultUserInfo==========" + resultUserInfo);
            } catch (Exception e) {
                throw new CustomExceptionHandler(20001, "获取用户信息失败");
            }
            //解析json
            HashMap<String, Object> mapUserInfo = gson.fromJson(resultUserInfo, HashMap.class);
            String nickname = (String)mapUserInfo.get("nickname");
            String headimgurl = (String)mapUserInfo.get("headimgurl");
            //向数据库中插入一条记录
            ucenterMember = new UcenterMember();
            ucenterMember.setNickname(nickname);
            ucenterMember.setOpenid(openid);
            ucenterMember.setAvatar(headimgurl);
            ucenterMemberService.save(ucenterMember);
        }//如果有值,直接登录即可

        // 生成jwt
        String token = JwtUtils.getJwtToken(ucenterMember.getId(),ucenterMember.getNickname());
        //存入cookie
        //CookieUtils.setCookie(request, response, "guli_jwt_token", token);
        //因为端口号不同存在蛞蝓问题,cookie不能跨域,所以这里使用url重写
        return "redirect:http://localhost:3000?token=" + token;
    }

十、前端整合测试

1、修改nginx(记得改完保存然后重启)

在这里插入图片描述

2、改代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

vue-qriously

猜你喜欢

转载自blog.csdn.net/grd_java/article/details/106490860