资料 |
---|
资料地址 |
后台管理系统目录 | 前台展示系统目录 |
---|---|
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
扫码后微信登录成功