https://open.weixin.qq.com
准备营业执照,1-2个工作日审批、300元
提交审核,7个工作日审批
参考文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN
获取access_token时序图

说明:微信登录二维码我们是以弹出层的形式打开,不是以页面形式,所以做法是不一样的,参考如下链接,上面有相关弹出层的方式
https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
如图:

因此我们的操作步骤为:
第一步我们通过接口把对应参数返回页面;
第二步在头部页面启动打开微信登录二维码;
第三步处理登录回调接口;
第四步回调返回页面通知微信登录层回调成功
第五步如果是第一次扫描登录,则绑定手机号码,登录成功
接下来我们根据步骤,一步一步实现
application.properties添加相关配置信息
# 微信开放平台 appid
wx.open.app_id=你的appid
# 微信开放平台 appsecret
wx.open.app_secret=你的appsecret
# 微信开放平台 重定向url
wx.open.redirect_url=http://你的服务器名称/api/ucenter/wx/callback
引入依赖
<dependencies>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>
dependencies>
创建util包,创建ConstantPropertiesUtil.java常量类
@Component
//@PropertySource("classpath:application.properties")
public class ConstantPropertiesUtil 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;
}
}
service_user微服务中创建api包
api包中创建WxApiController,返回微信参数
@Controller
@RequestMapping("/api/ucenter/wx")
public class WeixinApiController {
@Autowired
private UserInfoService userInfoService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取微信登录参数
*/
@GetMapping("getLoginParam")
@ResponseBody
public R genQrConnect(HttpSession session) throws UnsupportedEncodingException {
String redirectUri = URLEncoder.encode(ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL, "UTF-8");
Map<String, Object> map = new HashMap<>();
map.put("appid", ConstantPropertiesUtil.WX_OPEN_APP_ID);
map.put("redirectUri", redirectUri);
map.put("scope", "snsapi_login");
map.put("state", System.currentTimeMillis()+"");//System.currentTimeMillis()+""
return R.ok().data(map);
}
}
授权url参数说明
| 参数 | 是否必须 | 说明 |
|---|---|---|
| appid | 是 | 应用唯一标识 |
| redirect_uri | 是 | 请使用urlEncode对链接进行处理 |
| response_type | 是 | 填code |
| scope | 是 | 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即 |
| state | 否 | 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验 |
创建api/yygh/wx.js
import request from '@/utils/request'
const api_name = `/api/ucenter/wx`
export default {
getLoginParam() {
return request({
url: `${api_name}/getLoginParam`,
method: `get`
})
}
}
修改layouts/myheader.vue文件,添加微信二维码登录逻辑
// 1、引入api
import weixinApi from '@/api/yygh/wx'
//2、引入微信js
mounted() {
// 注册全局登录事件对象
window.loginEvent = new Vue();
// 监听登录事件
loginEvent.$on('loginDialogEvent', function () {
document.getElementById("loginDialog").click();
})
// 触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
//初始化微信js
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
document.body.appendChild(script)
// 微信登录回调处理
let self = this;
window["loginCallback"] = (name,token, openid) => {
self.loginCallback(name, token, openid);
}
},
//3、实例化微信JS对象
//添加微信登录方法
loginCallback(name, token, openid) {
// 打开手机登录层,绑定手机号,改逻辑与手机登录一致
if(openid != '') {
this.userInfo.openid = openid
this.showLogin()
} else {
this.setCookies(name, token)
}
},
weixinLogin() {
this.dialogAtrr.showLoginType = 'weixin'
weixinApi.getLoginParam().then(response => {
var obj = new WxLogin({
self_redirect:true,
id: 'weixinLogin', // 需要显示的容器id
appid: response.data.appid, // 公众号appid wx*******
scope: response.data.scope, // 网页默认即可
redirect_uri: response.data.redirectUri, // 授权成功后回调的url
state: response.data.state, // 可设置为简单的随机数加session用来校验
style: 'black', // 提供"black"、"white"可选。二维码的样式
href: '' // 外部css文件url,需要https
})
})
},
//说明:微信登录方法已绑定weixinLogin(),查看页面
# 微信开放平台 重定向url
wx.open.redirect_url=http://回调地址/api/ucenter/wx/callback
在WxApiController中添加方法
@GetMapping("callback")
public String callback(String code, String state, HttpSession session) {
//得到授权临时票据code
System.out.println("code = " + code);
System.out.println("state = " + state);
}
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
dependency>
放入util包
HttpClientUtils.java
在WxApiController.java中添加如下方法
@GetMapping("callback")
public String callback(String code, String state, HttpSession session) {
//第一步 获取临时票据 code
System.out.println("code:"+code);
//第二步 拿着code和微信id和秘钥,请求微信固定地址 ,得到两个值
//使用code和appid以及appscrect换取access_token
// %s 占位符
StringBuffer baseAccessTokenUrl = new StringBuffer()
.append("https://api.weixin.qq.com/sns/oauth2/access_token")
.append("?appid=%s")
.append("&secret=%s")
.append("&code=%s")
.append("&grant_type=authorization_code");
String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
ConstantPropertiesUtil.WX_OPEN_APP_ID,
ConstantPropertiesUtil.WX_OPEN_APP_SECRET,
code);
//使用httpclient请求这个地址
try {
String accesstokenInfo = HttpClientUtils.get(accessTokenUrl);
System.out.println("accesstokenInfo:"+accesstokenInfo);
//从返回字符串获取两个值 openid 和 access_token
JSONObject jsonObject = JSONObject.parseObject(accesstokenInfo);
String access_token = jsonObject.getString("access_token");
String openid = jsonObject.getString("openid");
//判断数据库是否存在微信的扫描人信息
//根据openid判断
UserInfo userInfo = userInfoService.selectWxInfoOpenId(openid);
if(userInfo == null) { //数据库不存在微信信息
//第三步 拿着openid 和 access_token请求微信地址,得到扫描人信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
String resultInfo = HttpClientUtils.get(userInfoUrl);
System.out.println("resultInfo:"+resultInfo);
JSONObject resultUserInfoJson = JSONObject.parseObject(resultInfo);
//解析用户信息
//用户昵称
String nickname = resultUserInfoJson.getString("nickname");
//用户头像
String headimgurl = resultUserInfoJson.getString("headimgurl");
//获取扫描人信息添加数据库
userInfo = new UserInfo();
userInfo.setNickName(nickname);
userInfo.setOpenid(openid);
userInfo.setStatus(1);
userInfoService.save(userInfo);
}
//返回name和token字符串
Map<String,String> map = new HashMap<>();
String name = userInfo.getName();
if(StringUtils.isEmpty(name)) {
name = userInfo.getNickName();
}
if(StringUtils.isEmpty(name)) {
name = userInfo.getPhone();
}
map.put("name", name);
//判断userInfo是否有手机号,如果手机号为空,返回openid
//如果手机号不为空,返回openid值是空字符串
//前端判断:如果openid不为空,绑定手机号,如果openid为空,不需要绑定手机号
if(StringUtils.isEmpty(userInfo.getPhone())) {
map.put("openid", userInfo.getOpenid());
} else {
map.put("openid", "");
}
//使用jwt生成token字符串
String token = JwtHelper.createToken(userInfo.getId(), name);
map.put("token", token);
//跳转到前端页面
return "redirect:http://localhost:3000/weixin/callback?token="+map.get("token")+ "&openid="+map.get("openid")+"&name="+URLEncoder.encode(map.get("name"),"utf-8");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
根据openid查询用户信息
//定义方法
UserInfo selectWxInfoOpenId(String openid);
//实现方法
@Override
public UserInfo selectWxInfoOpenId(String openid) {
return baseMapper.selectOne(new QueryWrapper<UserInfo>().eq("openid", openid));
}
绑定手机号操作
//登录接口
@Override
public Map<String, Object> login(LoginVo loginVo) {
//1 获取输入手机号和验证码
String phone = loginVo.getPhone();
String code = loginVo.getCode();
//2 手机号和验证码非空校验
if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
throw new YyghException(20001,"数据为空");
}
//验证码校验
// 输入的验证码 和 存储redis验证码比对
String redisCode = redisTemplate.opsForValue().get(phone);
if(!redisCode.equals(code)) {
throw new YyghException(20001,"验证码校验失败");
}
String openid = loginVo.getOpenid();
Map<String, Object> map = new HashMap<>();
if(StringUtils.isEmpty(openid)) {
//根据手机查询数据库
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("phone",phone);
UserInfo userInfo = baseMapper.selectOne(wrapper);
//如果返回对象为空,就是第一次登录,存到数据库登录数据
if(userInfo == null) {
userInfo = new UserInfo();
userInfo.setName("");
userInfo.setPhone(phone);
userInfo.setStatus(1);
baseMapper.insert(userInfo);
}
//判断用户是否可用
if(userInfo.getStatus() == 0) {
throw new YyghException(20001,"用户已经禁用");
}
map = get(userInfo);
} else {
//1 创建userInfo对象,用于存在最终所有数据
UserInfo userInfoFinal = new UserInfo();
//2 根据手机查询数据
// 如果查询手机号对应数据,封装到userInfoFinal
UserInfo userInfoPhone =
baseMapper.selectOne(new QueryWrapper<UserInfo>().eq("phone", phone));
if(userInfoPhone != null) {
// 如果查询手机号对应数据,封装到userInfoFinal
BeanUtils.copyProperties(userInfoPhone,userInfoFinal);
//把手机号数据删除
baseMapper.delete(new QueryWrapper<UserInfo>().eq("phone", phone));
}
//3 根据openid查询微信信息
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("openid",openid);
UserInfo userInfoWX = baseMapper.selectOne(wrapper);
//4 把微信信息封装userInfoFinal
userInfoFinal.setOpenid(userInfoWX.getOpenid());
userInfoFinal.setNickName(userInfoWX.getNickName());
userInfoFinal.setId(userInfoWX.getId());
//数据库表没有相同绑定手机号,设置值
if(userInfoPhone == null) {
userInfoFinal.setPhone(phone);
userInfoFinal.setStatus(userInfoWX.getStatus());
}
//修改手机号
baseMapper.updateById(userInfoFinal);
//5 判断用户是否锁定
if(userInfoFinal.getStatus() == 0) {
throw new YyghException(20001,"用户被锁定");
}
//6 登录后,返回登录数据
map = get(userInfoFinal);
}
return map;
}
private Map<String,Object> get(UserInfo userInfo) {
//返回页面显示名称
Map<String, Object> map = new HashMap<>();
String name = userInfo.getName();
if(StringUtils.isEmpty(name)) {
name = userInfo.getNickName();
}
if(StringUtils.isEmpty(name)) {
name = userInfo.getPhone();
}
map.put("name", name);
//根据userid和name生成token字符串
String token = JwtHelper.createToken(userInfo.getId(), name);
map.put("token", token);
return map;
}
说明:我们只期望返回一个空页面,然后跟登录层通信就可以了,其实就是一个过渡页面,所以我们要给这个过渡页面定义一个空模板
(1)添加空模板组件:/layouts/empty.vue
<template>
<div>
<nuxt/>
div>
template>
(2)创建回调返回页面 /pages/weixin/callback.vue
<template>
<div>
div>
template>
<script>
export default {
layout: "empty",
data() {
return {
}
},
mounted() {
let token = this.$route.query.token
let name = this.$route.query.name
let openid = this.$route.query.openid
// 调用父vue方法
window.parent['loginCallback'](name, token, openid)
}
}
script>
(3)在myheader.vue添加方法
mounted() {
// 注册全局登录事件对象
window.loginEvent = new Vue();
// 监听登录事件
loginEvent.$on('loginDialogEvent', function () {
document.getElementById("loginDialog").click();
})
// 触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
//初始化微信js
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
document.body.appendChild(script)
// 微信登录回调处理
let self = this;
window["loginCallback"] = (name,token, openid) => {
self.loginCallback(name, token, openid);
}
},
loginCallback(name, token, openid) {
// 打开手机登录层,绑定手机号,改逻辑与手机登录一致
if(openid != '') {
this.userInfo.openid = openid
this.showLogin()
} else {
this.setCookies(name, token)
}
},
7、预约挂号页面调整
修改/pages/hospital/_hoscode.vue组件
(1)引入cookie
import cookie from 'js-cookie'
(2)修改方法
schedule(depcode) {
// 登录判断
let token = cookie.get('token')
if (!token) {
loginEvent.$emit('loginDialogEvent')
return
}
window.location.href = '/hospital/schedule?hoscode=' + this.hospital.hoscode + "&depcode="+ depcode
}
逻辑与手机登录一致
if(openid != ‘’) {
this.userInfo.openid = openid
this.showLogin()
} else {
this.setCookies(name, token)
}
},
**7、预约挂号页面调整**
修改/pages/hospital/_hoscode.vue组件
**(1)引入cookie**
```javascript
import cookie from 'js-cookie'
(2)修改方法
schedule(depcode) {
// 登录判断
let token = cookie.get('token')
if (!token) {
loginEvent.$emit('loginDialogEvent')
return
}
window.location.href = '/hospital/schedule?hoscode=' + this.hospital.hoscode + "&depcode="+ depcode
}