2022年最新《谷粒学院开发教程》:8 - 前台登录功能

资料
资料地址
后台管理系统目录前台展示系统目录
1 - 构建工程篇7 - 渲染前台篇
2 - 前后交互篇8 - 前台登录篇
3 - 文件上传篇9 - 前台课程篇
4 - 课程管理篇10 - 前台支付篇
5 - 章节管理篇11 - 统计分析篇
6 - 微服务治理12 - 项目完结篇


一、整合JWT令牌

1、在common_utils模块中添加jwt工具依赖

<dependencies>
    <!-- JWT-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

2、资料复制JwtUtils和MD5工具类到该模块


二、整合阿里云短信服务

2.1、新建短信微服务

1、在service模块下创建子模块service_msm

2、配置类

# 服务器端口
server.port=8005
# 服务名
spring.application.name=service-msm
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# nacos注册中心
spring.cloud.nacos.discovery.server-addr=120.76.55.55:8848
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=/mapper/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# redis
spring.redis.host=120.76.55.55
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
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://120.76.55.55:3306/guli?useSSL=false&useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

3、启动类

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan("com.laptoy")
public class Service_edu_Main8005 {
    public static void main(String[] args) {
        SpringApplication.run(Service_edu_Main8005.class, args);
    }
}

2.2、开通阿里云短信服务

1、开通短信服务

开通测试短信服务

2、添加测试手机号 签名名称和模板Code为下述

在这里插入图片描述


2.3、编写发送短信接口

1、资料复制 生成随机数的工具类RandomUtils 放到common_utils模块

2、在service-msm的pom中引入依赖

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
    </dependency>
</dependencies>

3、编写控制层

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

    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //发送短信的方法
    @GetMapping("/send/{phone}")
    public R sendMsm(@PathVariable String phone) {
        //从redis获取验证码,如果能获取,直接返回
        String code = redisTemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
            return R.ok();
        }

        //获取不到就阿里云发送
        //生成随机值,并传递给阿里云短信,让他转发给手机
        code = RandomUtils.getSixBitRandom();
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", code);

        //调用service中发送短信的方法
        boolean isSend = msmService.sendMsm(map, phone);
        if (isSend) {
            //如果发送成功,把发送成功的code验证码保存到redis中,并设置有效时间,设置5分钟过期
            redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
            return R.ok();
        } else {
            return R.error().message("短信发送失败");
        }
    }
}

3、业务层

@Service
public class MsmServiceImpl implements MsmService {

    //发送短信的方法
    @Override
    public boolean sendMsm(HashMap<String, Object> map, String phone) {
        if (StringUtils.isEmpty(phone)) return false;
        //参数1:地域节点
        //参数2:AccessKey ID
        //参数3:AccessKey Secret
        DefaultProfile profile = DefaultProfile.getProfile("default", "LTAI5tL5FrVJBuQadij4KRvJ", "Xs7dHUvxCdHLd0K5iFK7NWEbdUN7GG");
        DefaultAcsClient client = new DefaultAcsClient(profile);

        //设置相关固定参数
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setSysMethod(MethodType.POST); //提交方式,默认不能改
        request.setSysDomain("dysmsapi.aliyuncs.com");//请求阿里云哪里,默认不能改
        request.setSysVersion("2017-05-25");//版本号
        request.setSysAction("SendSms");//请求哪个方法

        //设置发送相关参数
        request.putQueryParameter("PhoneNumbers", phone);//设置要发送的【手机号】
        request.putQueryParameter("SignName", "阿里云短信测试");//申请阿里云短信服务的【签名名称】
        request.putQueryParameter("TemplateCode", "SMS_154950909");//申请阿里云短信服务的【模版中的 模版CODE】

        //要求传递的code验证码为jason格式,可以使用JSONObject.toJSONString()将map转为json格式
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(map));

        //最终发送
        try {
            CommonResponse response = client.getCommonResponse(request);
            return response.getHttpResponse().isSuccess();
        } catch (ClientException e) {
            e.printStackTrace();
            return false;
        }

    }

}

三、用户登录注册接口

3.1、搭建微服务

1、在service模块下创建子模块 service_ucenter

2、资料 脚本guli_ucenter.sql 脚本生成数据

3、逆向生成代码

gc.setOutputDir("D:\\MyCode\\IdeaCode\\project\\gulicollege\\guli_parent\\service\\service_ucenter" + "/src/main/java"); //输出目录

pc.setModuleName("ucenter"); //模块名

strategy.setInclude("ucenter_member");//根据数据库哪张表生成,有多张表就加逗号继续填写

4、配置文件

# 服务端口
server.port=8006
# 服务名
spring.application.name=service-ucenter
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://120.76.55.55:3306/guli?useSSL=false&useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=00000
# redis
spring.redis.host=120.76.55.55
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=/mapper/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

5、启动类

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

6、实体类添加默认填充时间

public class Member implements Serializable {
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

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

3.2、创建登录和注册接口

1、创建LoginVo和RegisterVo用于数据封装

//登录对象
@Data
public class LoginVo {
    private String mobile;
    private String password;
}
@Data
public class RegisterVo {
    private String nickname;
    private String mobile;
    private String password;
    private String code;
}

2、控制层

@RestController
@RequestMapping("/ucenter/member")
@CrossOrigin
public class MemberController {
    @Autowired
    MemberService memberService;

    //登录
    @PostMapping("/login")
    public R login(@RequestBody LoginVo loginVo) {
        //返回token,使用jwt生成
        String token = memberService.login(loginVo);
        return R.ok().data("token", token);
    }

    //注册
    @PostMapping("/register")
    public R register(@RequestBody RegisterVo registerVo) {
        memberService.register(registerVo);
        return R.ok();
    }

}

3、业务层

@Service
public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> implements MemberService {

    @Autowired
    MemberMapper memberMapper;

    @Autowired
    StringRedisTemplate redisTemplate;

    //登录的方法
    @Override
    public String login(LoginVo loginVo) {
        //获取手机号和密码
        String mobile= loginVo.getMobile();
        String password = loginVo.getPassword();
        //判断输入的手机号和密码是否为空
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(mobile)) {
            throw new LaptoyException(20001, "手机号或密码为空");
        }

        //判断手机号是否正确
        QueryWrapper<Member> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile", mobile);
        Member ucenterMember = baseMapper.selectOne(wrapper);
        if (ucenterMember == null) {
            throw new LaptoyException(20001, "手机号不存在");
        }

        //判断密码是否正确
        // MD5加密是不可逆性的,不能解密,只能加密
        //将获取到的密码经过MD5加密与数据库比较
        if (!MD5.encrypt(password).equals(ucenterMember.getPassword())) {
            throw new LaptoyException(20001, "密码不正确");
        }

        //判断用户是否禁用
        if (ucenterMember.getIsDisabled()) {
            throw new LaptoyException(20001, "用户被禁用");
        }

        //生成jwtToken
        String token = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());

        return token;

    }

    //注册的方法
    @Override
    public void register(RegisterVo registerVo) {
        // 获取前端传来的数据
        String nickname = registerVo.getNickname(); //昵称
        String code = registerVo.getCode(); //验证码
        String mobile = registerVo.getMobile(); //手机号
        String password = registerVo.getPassword(); //密码

        // 非空判断
        if (StringUtils.isEmpty(nickname) || StringUtils.isEmpty(code) || StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
            throw new LaptoyException(20001, "传来的数据有空值,注册失败");
        }

        // 判断验证码
        // 获取redis验证码,根据手机号获取
        String redisCode = redisTemplate.opsForValue().get(mobile);
        if (!code.equals(redisCode)) {
            throw new LaptoyException(20001, "注册失败");
        }

        // 手机号不能重复
        QueryWrapper<Member> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile", mobile);
        Integer count = baseMapper.selectCount(wrapper);
        if (count >= 1) {
            throw new LaptoyException(20001, "手机号重复,注册失败");
        }

        // 数据添加到数据库中
        Member member = new Member();
        member.setPassword(MD5.encrypt(password));//密码加密
        member.setMobile(mobile);
        member.setNickname(nickname);
        member.setIsDisabled(false);//用户不禁用
        member.setAvatar("https://img-blog.csdnimg.cn/480b7a82bddb4f6a9b6942c88db18d85.png?imageView2/1/w/80/h/80");
        baseMapper.insert(member);

    }
}

3.3、创建接口根据token获取用户信息

在UcenterMemberController中创建方法

//根据token获取用户信息
@GetMapping("/getUserInfoForJwt")
public R getUserInfoForJwt(HttpServletRequest request) {
    //调用jwt工具类里面的根据request对象,获取头信息,返回用户id
    String id = JwtUtils.getMemberIdByJwtToken(request);
    //查询数据库,根据用户id,获取用户信息
    Member member = memberService.getById(id);
    return R.ok().data("userInfo", member);
}

四、用户登陆注册前端

4.1、安装插件

1、安装element-ui 和 vue-qriously

npm install element-ui
npm install vue-qriously

2、修改配置文件 plugins/nuxt-swiper-plugin.js,使用插件

import Vue from 'vue'
import VueAwesomeSwiper from '../node_modules/vue-awesome-swiper/dist/ssr'
import VueQriously from 'vue-qriously'
import ElementUI from 'element-ui' //element-ui的全部组件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI
Vue.use(VueQriously)
Vue.use(VueAwesomeSwiper)

3、创建布局给登录注册使用:layouts/sign.vue

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

使用的时候在对应页面指定布局,默认不指定就是用的default.vue

在这里插入图片描述

4、修改 layouts/default.vue 里的登录和注册地址,让其指向pages目录下对应的vue

在这里插入图片描述


4.2、用户注册功能

1、api/register.js

import request from '@/utils/request'

export default {
    //根据手机号码发送短信
    getMobile(mobile) {
        return request({
            url: `/msmservice/msm/send/${mobile}`,
            method: 'get'
        })
    },
    //用户注册
    register(formItem) {
        return request({
            url: `/ucenter/member/register`,
            method: 'post',
            data: formItem
        })
    }
}

2、pages/register.vue

<template>
  <div class="main">
    <div class="title">
      <a href="/login">登录</a>
      <span>·</span>
      <a class="active" href="/register">注册</a>
    </div>
    <div class="sign-up-container">
      <el-form ref="userForm" :model="params">
        <el-form-item class="input-prepend restyle" prop="nickname" :rules="[
            {
              required: true,
              message: '请输入你的昵称',
              trigger: 'blur',
            },
          ]">
          <div>
            <el-input type="text" placeholder="你的昵称" v-model="params.nickname" />
            <i class="iconfont icon-user" />
          </div>
        </el-form-item>
        <el-form-item class="input-prepend restyle no-radius" prop="mobile" :rules="[
            { required: true, message: '请输入手机号码', trigger: 'blur' },
            { validator: checkPhone, trigger: 'blur' },
          ]">
          <div>
            <el-input type="text" placeholder="手机号" v-model="params.mobile" />
            <i class="iconfont icon-phone" />
          </div>
        </el-form-item>
        <el-form-item class="input-prepend restyle no-radius" prop="code" :rules="[
            { required: true, message: '请输入验证码', trigger: 'blur' },
          ]">
          <div style="width: 100%; display: block; float: left; position: relative">
            <el-input type="text" placeholder="验证码" v-model="params.code" />
            <i class="iconfont icon-phone" />
          </div>
          <div class="btn" style="position: absolute; right: 0; top: 6px; width: 40%">
            <a href="javascript:" type="button" @click="getCodeFun()" :value="codeTest" style="border: none; background-color: none">{{ codeTest }}</a>
          </div>
        </el-form-item>
        <el-form-item class="input-prepend" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
          <div>
            <el-input type="password" placeholder="设置密码" v-model="params.password" />
            <i class="iconfont icon-password" />
          </div>
        </el-form-item>
        <div class="btn">
          <input type="button" class="sign-up-button" value="注册" @click="submitRegister()" />
        </div>
        <p class="sign-up-msg">
          点击 “注册” 即表示您同意并愿意遵守简书
          <br />
          <a target="_blank" href="http://www.jianshu.com/p/c44d171298ce">用户协 议</a><a target="_blank" href="http://www.jianshu.com/p/2ov8x3">隐私政策</a></p>
      </el-form>
      <!-- 更多注册方式 -->
      <div class="more-sign">
        <h6>社交帐号直接注册</h6>
        <ul>
          <li>
            <a id="weixin" class="weixin" target="_blank" href="http://huaan.free.idcfengye.com/api/ucenter/wx/login"><i class="iconfont icon-weixin" /></a>
          </li>
          <li>
            <a id="qq" class="qq" target="_blank" href="#"><i class="iconfont icon-qq" /></a>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>
<script>
import "~/assets/css/sign.css";
import "~/assets/css/iconfont.css";

import registerApi from "@/api/register";

export default {
  layout: "sign",
  data() {
    return {
      params: {
        mobile: "",
        code: "", //验证码
        nickname: "",
        password: "",
      },
      sending: true, //是否发送验证码
      second: 60, //倒计时间
      codeTest: "获取验证码",
    };
  },
  methods: {
    //通过输入的手机号,发送验证码
    getCodeFun() {
      //sending = false
      //his.sending原为true,请求成功,!this.sending == true,主要是防止有人把disabled属性去掉,多次点击;
      if (!this.sending) return;
      //debugger
      // prop 换成你想监听的prop字段
      this.$refs.userForm.validateField("mobile", (errMsg) => {
        if (errMsg == "") {
          registerApi.getMobile(this.params.mobile).then((res) => {
            this.sending = false;
            this.timeDown();
          });
        }
      });
    },
    //倒计时
    timeDown() {
      let result = setInterval(() => {
        --this.second;
        this.codeTest = this.second;
        if (this.second < 1) {
          clearInterval(result);
          this.sending = true;
          //this.disabled = false;
          this.second = 60;
          this.codeTest = "获取验证码";
        }
      }, 1000);
    },
    //注册提交的方法
    submitRegister() {
      this.$refs["userForm"].validate((valid) => {
        if (valid) {
          registerApi.register(this.params).then((response) => {
            //提示注册成功
            this.$message({
              type: "success",
              message: "注册成功",
            });
            this.$router.push({ path: "/login" });
          });
        }
      });
    },
    checkPhone(rule, value, callback) {
      //debugger
      if (!/^1[34578]\d{9}$/.test(value)) {
        return callback(new Error("手机号码格式不正确"));
      }
      return callback();
    },
  },
};
</script>

4.3、用户登录功能

1、api/login.js

import request from '@/utils/request'
export default {
    //登录
    submitLogin(userInfo) {
        return request({
            url: `/ucenter/member/login`,
            method: 'post',
            data: userInfo
        })
    },
    //根据token获取用户信息
    getLoginInfo() {
        return request({
            url: `/ucenter/member/getUserInfoForJwt/`,
            method: 'get',
            // headers: {'token': cookie.get('guli_token')}
        })
        //headers: {'token': cookie.get('guli_token')}
    }
}

2、安装 js-cookie 插件

npm install js-cookie

3、pages/login.vue

<template>
  <div class="main">
    <div class="title">
      <a class="active" href="/login">登录</a>
      <span>·</span>
      <a href="/register">注册</a>
    </div>
    <div class="sign-up-container">
      <el-form ref="userForm" :model="user">
        <el-form-item class="input-prepend restyle" prop="mobile" :rules="[
            {
              required: true,
              message: '请输入手机号码',
              trigger: 'blur',
            },
            { validator: checkPhone, trigger: 'blur' },
          ]">
          <div>
            <el-input type="text" placeholder="手机号" v-model="user.mobile" />
            <i class="iconfont icon-phone" />
          </div>
        </el-form-item>
        <el-form-item class="input-prepend" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
          <div>
            <el-input type="password" placeholder="密码" v-model="user.password" />
            <i class="iconfont icon-password" />
          </div>
        </el-form-item>
        <div class="btn">
          <input type="button" class="sign-in-button" value="登录" @click="submitLogin()" />
        </div>
      </el-form>
      <!-- 更多登录方式 -->
      <div class="more-sign">
        <h6>社交帐号登录</h6>
        <ul>
          <li>
            <a id="weixin" class="weixin" target="_blank" href="http://qy.free.idcfengye.com/api/ucenter/weixinLogin/login"><i class="iconfont icon-weixin" /></a>
          </li>
          <li>
            <a id="qq" class="qq" target="_blank" href="#"><i class="iconfont icon-qq" /></a>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
import "~/assets/css/sign.css";
import "~/assets/css/iconfont.css";
import cookie from "js-cookie";
import loginApi from "@/api/login";
export default {
  layout: "sign",
  data() {
    return {
      user: {
        //封装用于登录的用户对象
        mobile: "",
        password: "",
      },
      //用于获取接口传来的token中的对象
      loginInfo: {},
    };
  },
  methods: {
    submitLogin() {
      loginApi.submitLogin(this.user).then((response) => {
        if (response.data.success) {
          //把token存在cookie中、也可以放在localStorage中
          //参数1:cookie名称,参数2:具体的值,参数3:作用范围
          cookie.set("guli_token", response.data.data.token, {
            domain: "localhost",
          });
          //登录成功根据token获取用户信息
          loginApi.getLoginInfo().then((response) => {
            this.loginInfo = response.data.data.userInfo;
            //将用户信息记录cookie
            cookie.set("guli_ucenter", JSON.stringify(this.loginInfo), { domain: "localhost" });
            //跳转页面
            window.location.href = "/";
            //this.$router.push({path:'/'})
          });
        }
      });
    },
    checkPhone(rule, value, callback) {
      //debugger
      if (!/^1[34578]\d{9}$/.test(value)) {
        return callback(new Error("手机号码格式不正确"));
      }
      return callback();
    },
  },
};
</script>
<style>
.el-form-item__error {
  z-index: 9999999;
}
</style>

4.4、nginx配置

server {
	listen       9001;
	server_name  localhost;

	location ~ /eduservice/ {
		proxy_pass http://localhost:8001;
	}
	   
	location ~ /eduoss/ {
		proxy_pass http://localhost:8002;
	}
	
	location ~ /eduvod/ {
		proxy_pass http://localhost:8003;
	}
	
	location ~ /cmsservice/ {
		proxy_pass http://localhost:8004;
	}
	
	location ~ /msmservice/ {
		proxy_pass http://localhost:8005;
	}
	
	location ~ /ucenter/ {
		proxy_pass http://localhost:8006;
	}
}

4.5、拦截器配置

utils/request.js 用于传递token信息

import cookie from "js-cookie";

// http request 拦截器
service.interceptors.request.use(
    config => {
        //debugger
        //判断cookie中是否有名称叫 guli_token的数据
        if (cookie.get('guli_token')) {
            //把获取到的cookie值放到header中
            config.headers['token'] = cookie.get('guli_token');
        }
        return config
    },
    err => {
        return Promise.reject(err);
    })

4.6、分析登录流程

1、登录成功后调用接口获取用户信息,并将json格式的信息转为字符串存入cookie,之后跳转到index.vue页面,也就是 / (下图是login.vue)

在这里插入图片描述

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

2、index.vue使用的是default.vue格式模板(下图是default.vue)

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


五、OAuth2

OAuth2是一种授权框架,按照一定规则生成字符串,字符串包含用户信息,但是,他不提供具体的生成规则,他是一种解决方案
主要解决:1、开放系统间授权 ,2、分布式访问问题

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


六、整合微信二维码登录

1、微信开发者平台

2、流程

在这里插入图片描述


6.1、微信扫码登录

1、配置文件固定属性

# 微信开放平台 appid
wx.open.app_id=wxed9954c01bb89b47
# 微信开放平台 appsecret
wx.open.app_secret=a7482517235173ddb4083788de60b90e
# 微信开放平台 重定向url
wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback

2、创建常量类

@Component
public class ConstantProperties 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_APP_ID;
    public static String WX_APP_SECRET;
    public static String WX_REDIRECT_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_APP_ID = this.appID;
        WX_APP_SECRET = this.appSecret;
        WX_REDIRECT_URL = this.redirectUrl;
    }

}

3、控制层

@Controller //注意这里没有配置 @RestController
@CrossOrigin
@RequestMapping("/api/ucenter/wx")
public class WxApiController {

    //生成微信扫描二维码,   %s 相当于?占位符
    @GetMapping("/login")
    public String getWxCode() {
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect"
                + "?appid=%s"
                + "&redirect_uri=%s"
                + "&response_type=code"
                + "&scope=snsapi_login"
                + "&state=%s"
                + "#wechat_redirect";

        //对redirect_uri进行URLEncoder编码
        String redirect_uri = ConstantProperties.WX_REDIRECT_URL;
        try {
            redirect_uri = URLEncoder.encode(redirect_uri, "utf-8");//参数1:待编码字符串 参数2:编码方式
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        //设置 %s 占位符的参数,上面有3处
        String url = String.format(baseUrl,
                ConstantProperties.WX_APP_ID,
                redirect_uri,
                "Laptoy");

        //请求微信地址
        return "redirect:" + url;
    }

}

4、访问 http://localhost:8006/api/ucenter/wx/login

在这里插入图片描述

用户点击“确认登录”后,微信服务器会向谷粒学院的业务服务器发起回调,因此接下来我们需要开发回调controller


6.2、登录成功回调测试

用于测试,实际开发中,只需要修改下面的redirect_url的值指定跳转即可

这里是尚硅谷为了让我们测试到效果,所以指定了跳转到本地的8160端口的/api/ucenter/wx/callback路径

所以根据这个,下面就在controller中写一个这样子的方法用于扫码后跳转测试

300块开通费实则太贵,用别人的真香


1、修改本地端口号 和 重定向url

server.port=8160

wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback

2、nginx配置

location ~ /ucenter/ {
	proxy_pass http://localhost:8160;
}

3、测试接口

@GetMapping("/callback")
public String callback(String code, String state, HttpSession session) {
    //得到授权临时票据code
    System.out.println("code = " + code);
    System.out.println("state = " + state);
    return "redirect:http://localhost:3000";
}

4、访问 http://localhost:8160/api/ucenter/wx/login 登陆后会重定向到主页,并返回如下

code = 041ZlEFa11I3rD0mJDGa12aCss0ZlEFa
state = Laptoy

6.3、回调接口流程

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

在这里插入图片描述


6.4、回调接口开发

1、pom

<dependencies>
    <!--httpclient-->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
    <!--commons-io-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
    </dependency>
    <!--gson-->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>
</dependencies>

2、复制资料里的 HttpClientUtils 工具类

3、编写接口

@GetMapping("/callback")
public String callback(String code, String state, HttpSession session) {

    // 1、拿着code,去请求微信固定的地址,得到两个值 access_token 和 openid
    String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
            "?appid=%s" +
            "&secret=%s" +
            "&code=%s" +
            "&grant_type=authorization_code";

    // 2、拼接三个参数:id 秘钥 和 code值
    String accessTokenUrl = String.format(baseAccessTokenUrl,
            ConstantProperties.WX_APP_ID,
            ConstantProperties.WX_APP_SECRET,
            code);

    // 3、请求上面拼接好的地址,得到两个值 access_token 和 openid
    // 使用httpclient【不用浏览器,也能模拟器出浏览器的请求和响应过程】发送请求,得到返回的结果
    String accessTokenInfo = null;
    try {
        accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
        System.out.println("accessTokenInfo: " + accessTokenInfo);
    } catch (Exception e) {
        e.printStackTrace();
    }

    // 4、从accessTokenInfo中获取出  access_token 和 openid 的值
    // 将 accessTokenInfo 转换成 map集合,根据map的key 就可以获取对应的value
    // 使用json转换工具
    Gson gson = new Gson();
    HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
    String access_token = (String) mapAccessToken.get("access_token");
    String openid = (String) mapAccessToken.get("openid");

    // 5、判断数据库是否存在相同的微信内容
    Member member = memberService.getMemberByOpenId(openid);

    // 6、如果没有,则为新用户,添加数据库
    if (member == null) {
        // 拿着 access_token 和 openid 的值再去请求微信提供的固定地址
        String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                "?access_token=%s" +
                "&openid=%s";
        String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);

        String resultUserInfo = null;
        try {
            // 访问微信的资源服务器,获取用户信息
            resultUserInfo = HttpClientUtils.get(userInfoUrl);
            System.out.println(resultUserInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        HashMap<String, Object> resultMap = gson.fromJson(resultUserInfo, HashMap.class);
        String nickname = (String) resultMap.get("nickname");
        String headimgurl = (String) resultMap.get("headimgurl");

        //向数据库插入一条记录
        member = new Member();
        member.setNickname(nickname);
        member.setAvatar(headimgurl);
        member.setOpenid(openid);
        memberService.save(member);
    }

    //使用jwt根据member对象生成token字符串
    String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());

    //最后,返回首页,通过路径传递token字符串
    return "redirect:http://localhost:3000?token=" + jwtToken;
}

4、default.vue

created() {
  // 获取路径中token的值【用于微信二维码登录】
  this.token = this.$route.query.token;
  if (this.token) {
    // 判断路径中是否有token值
    this.wxLogin();
  }

  this.showInfo();
},
methods: {
  wxLogin() {
    // 设置token值
    cookie.set("guli_token", this.token, { domain: "localhost" });
    cookie.set("guli_ucenter", "", { domain: "localhost" });
    // 调用接口,根据token获取用户信息
    loginApi.getLoginInfo().then((resp) => {
      this.loginInfo = resp.data.data.userInfo;
      cookie.set("guli_ucenter", this.loginInfo, { domain: "localhost" });
    });
  },

5、访问 http://localhost:8160/api/ucenter/wx/login 扫码后微信登录成功

在这里插入图片描述


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laptoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值