011:基于Vue快速整合会员服务接口
1 构建前端Vue项目调用后端接口实现联合登录效果演示
今日课程任务
- 如何快速理解前后端分离架构模式
- 前后端分离架构模式还存在哪些缺点
- 快速构建Vue项目调用后端接口
- 前后端分离架构如何解决跨域的问题
- 基于Vue调用后端接口实现联合登录
- 如何快速部署Vue项目
2 什么是微服务前后端分离开发的模式
早期java项目 分成三层结构
com.mayikt.controller 单独web项目 ftl/jsp
com.mayikt.service
com.mayikt.dao
这种架构模式没有体现让专业的人做专业的事情
微服务架构提倡:让专业的人做专业的事情 前后端分离架构模式
如何理解前后端分离架构模式?
将以前控制层业务逻辑的操作全部交给前端开发实现,后端工程师主要开发接口被前端调用。
Vue项目的应用场景:
适用于移动端Web、微信公众号、企业级管理平台 (对搜索引擎seo不友好)
前后端分离架构模式存在哪些优缺点:
缺点:
1.联调测试 需要搭建局域网或者外网映射
2.沟通成本比较高 后端有变动需要通知前端
3.只适合比较大的互联网团队
4.跨域问题
优点:
后端开发不需要修改页面,让专业的人做专业的事情,效率更高
前后端分离架构前端页面如何部署?
前端项目实际上就是静态html+ajax调用接口绑定数据,放在nginx/tomcat服务器/cdn加速服务器中均可运行。
3 联合登录接口接口简单回顾
- 需要提供查询联合登录渠道接口 首页登录展示图标
- 登录接口改造,如果有传递openIdToken,关联到数据库中
- 根据openIdToken判断是否已经关联账号,如果关联则跳转到首页,没有关联则跳转到关联页面
查询渠道接口
数据库表meite_union_login新增字段union_image_log,存储渠道图标用于页面展示
UnionLoginDTO
@Data
public class UnionLoginDTO {
/**
* 手机号码
*/
@ApiModelProperty(value = "手机号码", name = "mobile", required = true)
private String mobile;
/**
* 密码
*/
@ApiModelProperty(value = "密码", name = "passWord", required = true)
private String passWord;
@ApiModelProperty(value = "开放平台令牌", name = "openIdToken", required = false)
private String openIdToken;
}
MemberUnionLoginService/MemberUnionLoginServiceImpl
/**
* 查询当前开通的渠道
*
* @return
*/
@GetMapping("/unionLoginList")
@ResponseBody
BaseResponse<List<UnionLoginDTO>> unionLoginList();
@Override
public BaseResponse<List<UnionLoginDTO>> unionLoginList() {
List<UnionLoginDO> unionLoginList = unionLoginMapper.selectByUnionLoginList();
if (unionLoginList == null) {
return setResultError("当前没有可用渠道");
}
List<UnionLoginDO> unionLoginDtos = MeiteBeanUtils.doToDtoList(unionLoginList, UnionLoginDO.class);
return setResultSuccess(unionLoginDtos);
}
测试效果:
4 前后端分离解决跨域的问题
前后端分离解决跨域的问题 来源浏览器的安全策略
浏览器地址访问http://127.0.0.1:8849/mayikt_mt_shop/login.html访问vue项目,
页面里面发出ajax请求必须要和地址栏的ip和端口必须要保持一致,否则浏览器会有安全策略问题。
ajax请求:http://127.0.0.1:7070/unionLoginList
解决跨域方案(浏览器访问端口号和页面请求的端口号不一致)
- 在响应头中设置允许跨域 只适合于小公司
响应配置response.setHeader(“Access-Control-Allow-Origin”,"*") - 使用HttpClient转发 效率低
- 使用jsonp处理 Jsonp不支持post请求,属于前端解决
- 使用Nginx解决跨域,保持域名和端口一致性
www.mayikt.com/vue 转发到vue项目
www.mayikt.com/api 转发到接口项目 - 可以直接在nginx中配置允许跨域的代码
“Access-Control-Allow-Origin”,"*" - 使用网关配置类似于nginx允许跨域的代码
- 使用SpringBoot注解形式@CrossOrigin
- 使用微服务网关也可以配置,配置浏览器访问的项目与接口项目的域名和端口号一致
5 Vue项目中构建登录页面加载网络
Vue前端页面
Axios 官方文档 https://www.kancloud.cn/yunye/axios/234845
login.html
<div class="logon_others"><span class="logon_others_title">其他登录方式</span>
<a class="logon_qq" v-for="(p) in dunionLogins" :href="p.requestAddress">
<img :src="p.unionImgLog" :alt="p.unionName"></a>
</div>
…
<script src="./js/union_login.js"></script>
union_login.js
var vue = new Vue({
el: "#app",
data: {
msg: "mayikt",
dunionLogins: []
},
created: function() {
this.loadUnionLoginList();
},
methods: {
loadUnionLoginList: function() {
var temp = this;
axios.get('http://127.0.0.1:7070/unionLoginList')
.then(function(response) {
// alert(response);
var data = response.data.data;
temp.dunionLogins = data;
console.log(response);
})
.catch(function(error) {
console.log(error);
});
}
}
})
测试效果:
6 回调接口需要Web层实现中转的原理
会员接口中直接写重定向代码到vue项目不符合规范,需要一个web项目作为中转。
通常情况应该起一个web-member项目调用会员服务接口,配置qq、微信回调地址为web接口地址,调用成功返回bindingurl页面,失败返回错误页面。
为了简化流程此处先将回调接口改为重定向到Vue项目
@GetMapping("/login/oauth/callback")
// public BaseResponse<JSONObject> unionLoginCallback(@RequestParam("unionPublicId") String unionPublicId);
public String unionLoginCallback(@RequestParam("unionPublicId") String unionPublicId);
@Controller
@CrossOrigin
public class MemberUnionLoginServiceImpl extends BaseApiService implements MemberUnionLoginService {
@Autowired
private UnionLoginMapper unionLoginMapper;
@Autowired
private TokenUtil tokenUtil;
@Value("${mayikt.login.vue.bindingurl}")
private String bindingurl;
@Override
public String unionLoginCallback(String unionPublicId) {
// 根据渠道id查询 联合基本信息
UnionLoginDO unionLoginDo = unionLoginMapper.selectByUnionLoginId(unionPublicId);
String unionBeanId = unionLoginDo.getUnionBeanId();
// 从Spring容器中根据beanid 查找到我们的策略类
UnionLoginStrategy unionLoginStrategy = SpringContextUtils.getBean(unionBeanId, UnionLoginStrategy.class);
// 根据当前线程获取request对象
HttpServletRequest request = ((ServletRequestAttributes)
(RequestContextHolder.currentRequestAttributes())).getRequest();
String openId = unionLoginStrategy.unionLoginCallback(request, unionLoginDo);
JSONObject jsonObject = new JSONObject();
jsonObject.put("openId", openId);
jsonObject.put("unionPublicId", unionPublicId);
String openToken = tokenUtil.createToken("mayikt.unionLogin.", jsonObject.toJSONString());
return "redirect:" + bindingurl + openToken;
}
}
bootstrap.yml
mayikt:
login:
vue:
bindingurl: http://127.0.0.1:8849/mayikt_mt_shop/relation_login.html?openIdToken=
7 前端获取Url中的token参数实现传递
relation_login.html 第三方登录页面
<script>
load();
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
function locationBindLogin() {
window.location.href = "bind_login.html?openIdToken=" + getQueryString('openIdToken');
}
function load() {
axios.get('http://127.0.0.1:7070/openIdToken?openIdToken=' + getQueryString('openIdToken'))
.then(function(response) {
var data = response.data;
if (data.code == 200) {
var userToken = data.data.userToken;
window.location.href = "index.html?userToken=" + userToken;
return;
}
})
.catch(function(error) {
console.log(error);
});
}
</script>
…
<body>
<div class="bind">
<div class="bind_contain">
<h1>确认关联小米账号</h1>
<img src="http://static.mayikt.com/QQ20191226.png" />
<p class="name">蚂蚁课堂创始人-余胜军</p>
<p>您的QQ尚未关联小米账号</p>
<div class="bind_bottom">
<a href="register.html">关联新账号</a>
<a class="old_" href="javascript:void(0);" onclick="locationBindLogin()">关联到已有账号</a>
</div>
</div>
</div>
</body>
bind_login.html 绑定账号页面
<body>
<div class="Joint">
<a class="logon_logo">
<img src="http://static.mayikt.com/logomayishangcheng.png" />
</a>
<div class="Joint_container" id="app">
<h2>请登录您要关联的账号</h2>
<div class="regbox">
<div class="el-input el-input--suffix">
<input type="text" autocomplete="off"
v-model="phone"
placeholder="账号" class="el-input__inner">
</div>
<div class="el-input el-input--suffix">
<input type="password"
v-model="passWord"
autocomplete="off" placeholder="密码" class="el-input__inner">
<span class="el-input__suffix"><span class="el-input__suffix-inner">
</span>
</span>
</div>
</div>
<button type="button" @click="login()" class="el-button el-button--default">
<span >绑定并登录</span>
</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
</script>
<script src="js/bind_login.js"></script>
</body>
bind_login.js
var vue = new Vue({
el: "#app",
data: {
phone: "",
passWord: ""
},
methods: {
login: function() {
var temp = this;
axios.post('http://127.0.0.1:7070/login', {
mobile: this.phone,
passWord: this.passWord,
openIdToken: getQueryString("openIdToken")
}, {
headers: {
'X-Real-IP': '127.0.0.1',
'channel': 'PC',
'deviceInfor': 'huawei8'
}
})
.then(function(response) {
var data = response.data;
if (data.code != 200) {
alert(data.msg);
return;
}
alert('登录成功');
var userToken = data.data.userToken;
window.location.href = "index.html?userToken=" + userToken;
})
.catch(function(error) {
console.log(error);
});
}
}
})
测试效果:
填入账号密码调用后台login接口传递openIdToken,异步方法AsyncLoginLogManage更新数据库绑定账户openId。
8 Vue整合后端微服务接口实现联合登录演示
测试效果:
注意:实现类MemberOpenIdTokenLoginImpl/MemberUnionLoginServiceImpl/
MemberLoginServiceImpl上面都要加上注解@CrossOrigin
9 微信联合登录生成二维码说明
整体流程同qq联合登录类似,不同之处在于点击微信登录图标前端页面显示二维码(后端调用生成二维码接口返回给前端),用户微信扫码相当于QQ点击头像授权操作,然后账户未绑定则更新openId,如果已绑定直接跳转到首页。