小言_互联网的博客

尚医通-OAuth2-微信登录接口开发(三十一)

808人阅读  评论(0)

目录:

(1)微信登录-OAuth2介绍

(2)前台用户系统-微信登录-准备工作

(3)微信登录-生成微信二维码-接口开发

(4)微信登录-生成验证码-前端整合

(5)微信登录-获取扫码人信息-实现分析

(6)微信登录-获取联系人扫码信息-接口开发

(7)微信登录-手机号绑定和前端整合


(1)微信登录-OAuth2介绍

微信登录是基于理论知识OAuth2实现的

1、OAuth2

OAuth2解决什么问题

1.1.1 开放系统间授权

照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源

资源拥有者:照片拥有者

客户应用:云冲印

受保护的资源:照片

1.1.3方式一:用户名密码复制 

 

用户将自己的"云存储"服务的用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

(1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全

(2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全

(3)"云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。

(4)用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

(5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

总结:

将受保护的资源中的用户名和密码存储在客户应用的服务器上,使用时直接使用这个用户名和密码登录

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

1.1.4方式二:通用开发者key

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

 

1.1.5方式三:颁发令牌

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

使用令牌:字符串 

令牌类比仆从钥匙 

 

 令牌:就是按照约定的规则,生成字符串,这个字符串颁发给某个服务,它它拿着进行访问,

设置字符串的有效时间,随时去吊销这个字符串,令牌的颁发我们令牌字符串我们需要对它进行加密等等处理

OAth2它只约定我们生成颁发令牌,生成字符串,但是字符串按照什么规则生成,它并没有约定,只告诉你用字符串方式解决,具体可以像之前的一个工具JWT工具生成字符串的的规则

OAth2只约定解决方案,并没有约定怎么去做,它只是颁发令牌、设置令牌的有效时间、包括随时去解除这个令牌

 

1.3 OAuth2的应用

1.3.1 微服务安全

现代微服务中系统微服务化以及应用的形态和设备类型增多,不能用传统的登录方式

核心的技术不是用户名和密码,而是token,由AuthServer颁发token,用户使用token进行登录

1.3.2 社交登录

(2)前台用户系统-微信登录-准备工作

 点击微信扫码登录按钮,出现二维码,需要进行扫描,扫描二维码,获得扫码的信息,然后再出现绑定手机号,然后数据最后添加到数据库中

 我们用到微信的相关操作,微信是腾讯开发的,需要调用腾讯那边的内容进行实现,需要先在微信的开放平台省注册一个用户,注册一个用户的目的就是让你有微信相关接口的权限,让他给你做一个授权,注册目前只支持企业级别的用户,个人用户不支持

2.1 前期准备

1、注册

微信开放平台:https://open.weixin.qq.com

2、邮箱激活

3、完善开发者资料

4、开发者资质认证

准备营业执照,1-2个工作日审批、300元

5、创建网站应用

提交审核,7个工作日审批

6、内网穿透

ngrok的使用

注册之后会提供一个id和秘钥,这里我们使用提供的,自己不在注册

redirect_url: 创建一个网站应用,在二维码下面显示,网站名称,这个名字需要微信那边审核通过

 域名:扫码确认之后,完成扫码,完成扫码之后,得到扫码的信息,微信那端给我们做一个回调,扫码之后,会调用指定接口的路径,这个路径我们没有办法调用本地,默认找不到,需要找到一个网络返回的的域名,才能返回,这就需要一个能够返回的域名

获取access_token时序图

第一步:请求CODE(生成授权URL)

第二步:通过code获取access_token(开发回调URL)

 在service_user模块中添加:微信的配置

创建一个工具类读取配置文件刚才配置的内容:


  
  1. package com. atguigu. yygh. user. utils;
  2. import org. springframework. beans. factory. InitializingBean;
  3. import org. springframework. beans. factory. annotation. Value;
  4. import org. springframework. stereotype. Component;
  5. @Component
  6. public class ConstantWxPropertiesUtils implements InitializingBean {
  7. @Value( "${wx.open.app_id}")
  8. private String appId;
  9. @Value( "${wx.open.app_secret}")
  10. private String appSecret;
  11. @Value( "${wx.open.redirect_url}")
  12. private String redirectUrl;
  13. @Value( "${yygh.baseUrl}")
  14. private String yyghBaseUrl;
  15. public static String WX_OPEN_APP_ID;
  16. public static String WX_OPEN_APP_SECRET;
  17. public static String WX_OPEN_REDIRECT_URL;
  18. public static String YYGH_BASE_URL;
  19. @Override
  20. public void afterPropertiesSet() throws Exception {
  21. WX_OPEN_APP_ID = appId;
  22. WX_OPEN_APP_SECRET = appSecret;
  23. WX_OPEN_REDIRECT_URL = redirectUrl;
  24. YYGH_BASE_URL = yyghBaseUrl;
  25. }
  26. }

 创建controller:WeixinApiController 


  
  1. package com.atguigu.yygh.user.api;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind. annotation.RequestMapping;
  4. //微信操作的接口
  5. @Controller
  6. @RequestMapping("/api/ucenter/wx")
  7. public class WeixinApiController {
  8. //1 生成微信扫描的二维码
  9. //2 回调的方法,得到扫描人的信息
  10. }

 (3)微信登录-生成微信二维码-接口开发

操作模块:service-user

说明:微信登录二维码我们是以弹出层的形式打开,不是以页面形式,所以做法是不一样的,参考如下链接,上面有相关弹出层的方式

准备工作 | 微信开放文档微信开发者平台文档https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

如图:

 

因此我们的操作步骤为:

第一步我们通过接口把对应参数返回页面;

第二步在头部页面启动打开微信登录二维码;

第三步处理登录回调接口;

第四步回调返回页面通知微信登录层回调成功

第五步如果是第一次扫描登录,则绑定手机号码,登录成功

接下来我们根据步骤,一步一步实现

 接下来我们写接口返回参数:

在:WeixinApiController :中添加方法:


  
  1. package com.atguigu.yygh.user.api;
  2. import com.atguigu.yygh.common.result.Result;
  3. import com.atguigu.yygh.user.utils.ConstantWxPropertiesUtils;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.ResponseBody;
  8. import javax.servlet.http.HttpSession;
  9. import java.io.UnsupportedEncodingException;
  10. import java.net.URLEncoder;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. //微信操作的接口
  14. @Controller
  15. @RequestMapping("/api/ucenter/wx")
  16. public class WeixinApiController {
  17. //1 生成微信扫描的二维码
  18. //返回生成二维码需要的参数
  19. @GetMapping("getLoginParam")
  20. @ResponseBody
  21. public Result genQrConnect (HttpSession session) throws UnsupportedEncodingException {
  22. Map<String, Object> map = new HashMap<>();
  23. map.put( "appid", ConstantWxPropertiesUtils.WX_OPEN_APP_ID);
  24. String redirectUri = URLEncoder.encode(ConstantWxPropertiesUtils.WX_OPEN_REDIRECT_URL, "UTF-8");
  25. map.put( "redirectUri", redirectUri);
  26. map.put( "scope", "snsapi_login");
  27. map.put( "state", System.currentTimeMillis()+ ""); //System.currentTimeMillis()+""
  28. return Result.ok(map);
  29. }
  30. //2 回调的方法,得到扫描人的信息
  31. }

(4)微信登录-生成验证码-前端整合

参数成功返回: 

 创建weixin.js:


  
  1. import request from '@/utils/request'
  2. const api_name = `/api/ucenter/wx`
  3. export default {
  4. getLoginParam( ) {
  5. return request({
  6. url: `${api_name}/getLoginParam`,
  7. method: `get`
  8. })
  9. }
  10. }

在myheader.vue中引入这个js:

 

在mounted里面添加初始化微信js

 书写点击微信Login方法

 

 在网关加上这个访问的路径:

测试点击如果验证码没有出来,修改配置文件:

点击下方的微信登录:出现二维码:

 微信那段扫描我们的二维码,微信那端会回调咱的方法,咱的方法中会得到扫码人的信息,把信息加到数据库中,在这过程中微信要给它绑定一个手机号最终完成操作

(5)微信登录-获取扫码人信息-实现分析

修改service_user模块的端口号:8106

 (6)微信登录-获取联系人扫码信息-接口开发

 httpclient请求微信提供的地址,原来是在地址栏输入地址回车请求,目前是程序中做到,不是用浏览器请求,所以用到这个技术,可以简单理解为不需要浏览器,帮助我们请求地址,然后得到地址返回的数据,或得到请求接口返回的数据,这就叫httpclient模拟浏览器请求和响应的过程

在common-util中引入依赖:

 

创建工具类:HttpClientUtils 


  
  1. package com.atguigu.yygh.user.utils;
  2. import org.apache.commons.io.IOUtils;
  3. import org.apache.commons.lang.StringUtils;
  4. import org.apache.http.Consts;
  5. import org.apache.http.HttpEntity;
  6. import org.apache.http.HttpResponse;
  7. import org.apache.http.NameValuePair;
  8. import org.apache.http.client.HttpClient;
  9. import org.apache.http.client.config.RequestConfig;
  10. import org.apache.http.client.config.RequestConfig.Builder;
  11. import org.apache.http.client.entity.UrlEncodedFormEntity;
  12. import org.apache.http.client.methods.HttpGet;
  13. import org.apache.http.client.methods.HttpPost;
  14. import org.apache.http.conn.ConnectTimeoutException;
  15. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  16. import org.apache.http.conn.ssl.SSLContextBuilder;
  17. import org.apache.http.conn.ssl.TrustStrategy;
  18. import org.apache.http.conn.ssl.X509HostnameVerifier;
  19. import org.apache.http.entity.ContentType;
  20. import org.apache.http.entity.StringEntity;
  21. import org.apache.http.impl.client.CloseableHttpClient;
  22. import org.apache.http.impl.client.HttpClients;
  23. import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
  24. import org.apache.http.message.BasicNameValuePair;
  25. import javax.net.ssl.SSLContext;
  26. import javax.net.ssl.SSLException;
  27. import javax.net.ssl.SSLSession;
  28. import javax.net.ssl.SSLSocket;
  29. import java.io.IOException;
  30. import java.net.SocketTimeoutException;
  31. import java.security.GeneralSecurityException;
  32. import java.security.cert.CertificateException;
  33. import java.security.cert.X509Certificate;
  34. import java.util.ArrayList;
  35. import java.util.List;
  36. import java.util.Map;
  37. import java.util.Map.Entry;
  38. import java.util.Set;
  39. public class HttpClientUtils {
  40. public static final int connTimeout= 10000;
  41. public static final int readTimeout= 10000;
  42. public static final String charset= "UTF-8";
  43. private static HttpClient client = null;
  44. static {
  45. PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
  46. cm.setMaxTotal( 128);
  47. cm.setDefaultMaxPerRoute( 128);
  48. client = HttpClients.custom().setConnectionManager(cm).build();
  49. }
  50. public static String postParameters (String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
  51. return post(url,parameterStr, "application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
  52. }
  53. public static String postParameters (String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
  54. return post(url,parameterStr, "application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
  55. }
  56. public static String postParameters (String url, Map<String, String> params) throws ConnectTimeoutException,
  57. SocketTimeoutException, Exception {
  58. return postForm(url, params, null, connTimeout, readTimeout);
  59. }
  60. public static String postParameters (String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
  61. SocketTimeoutException, Exception {
  62. return postForm(url, params, null, connTimeout, readTimeout);
  63. }
  64. public static String get (String url) throws Exception {
  65. return get(url, charset, null, null);
  66. }
  67. public static String get (String url, String charset) throws Exception {
  68. return get(url, charset, connTimeout, readTimeout);
  69. }
  70. /**
  71. * 发送一个 Post 请求, 使用指定的字符集编码.
  72. *
  73. * @param url
  74. * @param body RequestBody
  75. * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
  76. * @param charset 编码
  77. * @param connTimeout 建立链接超时时间,毫秒.
  78. * @param readTimeout 响应超时时间,毫秒.
  79. * @return ResponseBody, 使用指定的字符集编码.
  80. * @throws ConnectTimeoutException 建立链接超时异常
  81. * @throws SocketTimeoutException 响应超时
  82. * @throws Exception
  83. */
  84. public static String post (String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
  85. throws ConnectTimeoutException, SocketTimeoutException, Exception {
  86. HttpClient client = null;
  87. HttpPost post = new HttpPost(url);
  88. String result = "";
  89. try {
  90. if (StringUtils.isNotBlank(body)) {
  91. HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
  92. post.setEntity(entity);
  93. }
  94. // 设置参数
  95. RequestConfig. Builder customReqConf = RequestConfig.custom();
  96. if (connTimeout != null) {
  97. customReqConf.setConnectTimeout(connTimeout);
  98. }
  99. if (readTimeout != null) {
  100. customReqConf.setSocketTimeout(readTimeout);
  101. }
  102. post.setConfig(customReqConf.build());
  103. HttpResponse res;
  104. if (url.startsWith( "https")) {
  105. // 执行 Https 请求.
  106. client = createSSLInsecureClient();
  107. res = client.execute(post);
  108. } else {
  109. // 执行 Http 请求.
  110. client = HttpClientUtils.client;
  111. res = client.execute(post);
  112. }
  113. result = IOUtils.toString(res.getEntity().getContent(), charset);
  114. } finally {
  115. post.releaseConnection();
  116. if (url.startsWith( "https") && client != null&& client instanceof CloseableHttpClient) {
  117. ((CloseableHttpClient) client).close();
  118. }
  119. }
  120. return result;
  121. }
  122. /**
  123. * 提交form表单
  124. *
  125. * @param url
  126. * @param params
  127. * @param connTimeout
  128. * @param readTimeout
  129. * @return
  130. * @throws ConnectTimeoutException
  131. * @throws SocketTimeoutException
  132. * @throws Exception
  133. */
  134. public static String postForm (String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
  135. SocketTimeoutException, Exception {
  136. HttpClient client = null;
  137. HttpPost post = new HttpPost(url);
  138. try {
  139. if (params != null && !params.isEmpty()) {
  140. List<NameValuePair> formParams = new ArrayList<NameValuePair>();
  141. Set<Entry<String, String>> entrySet = params.entrySet();
  142. for (Entry<String, String> entry : entrySet) {
  143. formParams.add( new BasicNameValuePair(entry.getKey(), entry.getValue()));
  144. }
  145. UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
  146. post.setEntity(entity);
  147. }
  148. if (headers != null && !headers.isEmpty()) {
  149. for (Entry<String, String> entry : headers.entrySet()) {
  150. post.addHeader(entry.getKey(), entry.getValue());
  151. }
  152. }
  153. // 设置参数
  154. Builder customReqConf = RequestConfig.custom();
  155. if (connTimeout != null) {
  156. customReqConf.setConnectTimeout(connTimeout);
  157. }
  158. if (readTimeout != null) {
  159. customReqConf.setSocketTimeout(readTimeout);
  160. }
  161. post.setConfig(customReqConf.build());
  162. HttpResponse res = null;
  163. if (url.startsWith( "https")) {
  164. // 执行 Https 请求.
  165. client = createSSLInsecureClient();
  166. res = client.execute(post);
  167. } else {
  168. // 执行 Http 请求.
  169. client = HttpClientUtils.client;
  170. res = client.execute(post);
  171. }
  172. return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
  173. } finally {
  174. post.releaseConnection();
  175. if (url.startsWith( "https") && client != null
  176. && client instanceof CloseableHttpClient) {
  177. ((CloseableHttpClient) client).close();
  178. }
  179. }
  180. }
  181. /**
  182. * 发送一个 GET 请求
  183. */
  184. public static String get (String url, String charset, Integer connTimeout,Integer readTimeout)
  185. throws ConnectTimeoutException,SocketTimeoutException, Exception {
  186. HttpClient client = null;
  187. HttpGet get = new HttpGet(url);
  188. String result = "";
  189. try {
  190. // 设置参数
  191. Builder customReqConf = RequestConfig.custom();
  192. if (connTimeout != null) {
  193. customReqConf.setConnectTimeout(connTimeout);
  194. }
  195. if (readTimeout != null) {
  196. customReqConf.setSocketTimeout(readTimeout);
  197. }
  198. get.setConfig(customReqConf.build());
  199. HttpResponse res = null;
  200. if (url.startsWith( "https")) {
  201. // 执行 Https 请求.
  202. client = createSSLInsecureClient();
  203. res = client.execute(get);
  204. } else {
  205. // 执行 Http 请求.
  206. client = HttpClientUtils.client;
  207. res = client.execute(get);
  208. }
  209. result = IOUtils.toString(res.getEntity().getContent(), charset);
  210. } finally {
  211. get.releaseConnection();
  212. if (url.startsWith( "https") && client != null && client instanceof CloseableHttpClient) {
  213. ((CloseableHttpClient) client).close();
  214. }
  215. }
  216. return result;
  217. }
  218. /**
  219. * 从 response 里获取 charset
  220. */
  221. @SuppressWarnings("unused")
  222. private static String getCharsetFromResponse (HttpResponse ressponse) {
  223. // Content-Type:text/html; charset=GBK
  224. if (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
  225. String contentType = ressponse.getEntity().getContentType().getValue();
  226. if (contentType.contains( "charset=")) {
  227. return contentType.substring(contentType.indexOf( "charset=") + 8);
  228. }
  229. }
  230. return null;
  231. }
  232. /**
  233. * 创建 SSL连接
  234. * @return
  235. * @throws GeneralSecurityException
  236. */
  237. private static CloseableHttpClient createSSLInsecureClient () throws GeneralSecurityException {
  238. try {
  239. SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial( null, new TrustStrategy() {
  240. public boolean isTrusted (X509Certificate[] chain,String authType) throws CertificateException {
  241. return true;
  242. }
  243. }).build();
  244. SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {
  245. @Override
  246. public boolean verify (String arg0, SSLSession arg1) {
  247. return true;
  248. }
  249. @Override
  250. public void verify (String host, SSLSocket ssl)
  251. throws IOException {
  252. }
  253. @Override
  254. public void verify (String host, X509Certificate cert)
  255. throws SSLException {
  256. }
  257. @Override
  258. public void verify (String host, String[] cns,
  259. String[] subjectAlts) throws SSLException {
  260. }
  261. });
  262. return HttpClients.custom().setSSLSocketFactory(sslsf).build();
  263. } catch (GeneralSecurityException e) {
  264. throw e;
  265. }
  266. }
  267. }

 UserInfoService 接口: 


  
  1. package com.atguigu.yygh.user.service;
  2. import com.atguigu.yygh.model.user.UserInfo;
  3. import com.atguigu.yygh.vo.user.LoginVo;
  4. import com.baomidou.mybatisplus. extension.service.IService;
  5. import java.util. Map;
  6. public interface UserInfoService extends IService<UserInfo> {
  7. //用户手机登录接口
  8. Map< String, Object> login(LoginVo loginVo);
  9. //根据openid判断数据库是否存在微信的扫描人信息
  10. UserInfo selectWxInfoOpenId( String openid);
  11. }

实现类: UserInfoServiceImpl:添加实现方法:

 在WexinApiController:微信扫码回调方法


  
  1. package com.atguigu.yygh.user.api;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.atguigu.yygh.common.helper.JwtHelper;
  4. import com.atguigu.yygh.common.result.Result;
  5. import com.atguigu.yygh.model.user.UserInfo;
  6. import com.atguigu.yygh.user.service.UserInfoService;
  7. import com.atguigu.yygh.user.utils.ConstantWxPropertiesUtils;
  8. import com.atguigu.yygh.user.utils.HttpClientUtils;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.stereotype.Controller;
  11. import org.springframework.util.StringUtils;
  12. import org.springframework.web.bind.annotation.GetMapping;
  13. import org.springframework.web.bind.annotation.RequestMapping;
  14. import org.springframework.web.bind.annotation.ResponseBody;
  15. import javax.servlet.http.HttpSession;
  16. import java.io.UnsupportedEncodingException;
  17. import java.net.URLEncoder;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. //微信操作的接口
  21. @Controller
  22. @RequestMapping("/api/ucenter/wx")
  23. public class WeixinApiController {
  24. @Autowired
  25. private UserInfoService userInfoService;
  26. //1 生成微信扫描的二维码
  27. //返回生成二维码需要的参数
  28. @GetMapping("getLoginParam")
  29. @ResponseBody
  30. public Result genQrConnect (HttpSession session) throws UnsupportedEncodingException {
  31. Map<String, Object> map = new HashMap<>();
  32. map.put( "appid", ConstantWxPropertiesUtils.WX_OPEN_APP_ID);
  33. String redirect_Uri = URLEncoder.encode(ConstantWxPropertiesUtils.WX_OPEN_REDIRECT_URL, "UTF-8");
  34. map.put( "redirect_Uri", redirect_Uri);
  35. map.put( "scope", "snsapi_login");
  36. map.put( "state", System.currentTimeMillis()+ ""); //System.currentTimeMillis()+""
  37. return Result.ok(map);
  38. }
  39. //微信扫描后回调的方法
  40. @GetMapping("callback") //code和state是回调函数请求过来的时候携带的2个参数
  41. public String callback (String code,String state) {
  42. //第一步 获取临时票据 code
  43. System.out.println( "code:"+code);
  44. //第二步 拿着code和微信id和秘钥,请求微信固定地址 ,得到两个值
  45. //使用code和appid以及appscrect换取access_token字符串
  46. // %s 占位符
  47. StringBuffer baseAccessTokenUrl = new StringBuffer()
  48. .append( "https://api.weixin.qq.com/sns/oauth2/access_token")
  49. .append( "?appid=%s")
  50. .append( "&secret=%s")
  51. .append( "&code=%s")
  52. .append( "&grant_type=authorization_code");
  53. String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
  54. ConstantWxPropertiesUtils.WX_OPEN_APP_ID,
  55. ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET,
  56. code);
  57. //使用httpclient请求这个地址
  58. try {
  59. String accesstokenInfo = HttpClientUtils.get(accessTokenUrl);
  60. System.out.println( "accesstokenInfo:"+accesstokenInfo);
  61. //从返回字符串获取两个值 openid 和 access_token
  62. JSONObject jsonObject = JSONObject.parseObject(accesstokenInfo);
  63. String access_token = jsonObject.getString( "access_token");
  64. String openid = jsonObject.getString( "openid");
  65. //判断数据库是否存在微信的扫描人信息
  66. //根据openid判断
  67. UserInfo userInfo = userInfoService.selectWxInfoOpenId(openid);
  68. if(userInfo == null) { //数据库不存在微信信息
  69. //第三步 拿着openid 和 access_token请求微信地址,得到扫描人信息
  70. String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
  71. "?access_token=%s" +
  72. "&openid=%s";
  73. String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
  74. String resultInfo = HttpClientUtils.get(userInfoUrl);
  75. System.out.println( "resultInfo:"+resultInfo);
  76. JSONObject resultUserInfoJson = JSONObject.parseObject(resultInfo);
  77. //解析用户信息
  78. //用户昵称
  79. String nickname = resultUserInfoJson.getString( "nickname");
  80. //用户头像
  81. String headimgurl = resultUserInfoJson.getString( "headimgurl");
  82. //获取扫描人信息添加数据库
  83. userInfo = new UserInfo();
  84. userInfo.setNickName(nickname);
  85. userInfo.setOpenid(openid);
  86. userInfo.setStatus( 1);
  87. userInfoService.save(userInfo);
  88. }
  89. //返回name和token字符串
  90. Map<String,String> map = new HashMap<>();
  91. String name = userInfo.getName();
  92. if(StringUtils.isEmpty(name)) {
  93. name = userInfo.getNickName();
  94. }
  95. if(StringUtils.isEmpty(name)) {
  96. name = userInfo.getPhone();
  97. }
  98. map.put( "name", name);
  99. //判断userInfo是否有手机号,如果手机号为空,返回openid
  100. //如果手机号不为空,返回openid值是空字符串
  101. //前端判断:如果openid不为空,绑定手机号,如果openid为空,不需要绑定手机号
  102. if(StringUtils.isEmpty(userInfo.getPhone())) {
  103. map.put( "openid", userInfo.getOpenid());
  104. } else {
  105. map.put( "openid", "");
  106. }
  107. //使用jwt生成token字符串
  108. String token = JwtHelper.createToken(userInfo.getId(), name);
  109. map.put( "token", token);
  110. //跳转到前端页面
  111. return "redirect:" + ConstantWxPropertiesUtils.YYGH_BASE_URL + "/weixin/callback?token="+map.get( "token")+ "&openid="+map.get( "openid")+ "&name="+URLEncoder.encode(map.get( "name"), "utf-8");
  112. } catch (Exception e) {
  113. e.printStackTrace();
  114. return null;
  115. }
  116. }
  117. }

(7)微信登录-手机号绑定和前端整合

创建weixin callback.vue 


  
  1. <template>
  2. <!-- header -->
  3. <div> </div>
  4. <!-- footer -->
  5. </template>
  6. <script>
  7. export default {
  8. layout: "empty",
  9. data( ) {
  10. return {};
  11. },
  12. mounted( ) {
  13. let token = this. $route. query. token;
  14. let name = this. $route. query. name;
  15. let openid = this. $route. query. openid;
  16. // 调用父vue方法
  17. window. parent[ "loginCallback"](name, token, openid);
  18. },
  19. };
  20. </script>

这是一个中转页面,得到参数值,再调用弹框里面的方法,一会在myheader弹框中写上这个回调方法loginCallback,callback做一个中转页面,由页面在跳到弹框里面更加方便

在myheader.vue中添加:

myheader.vue详细代码:


  
  1. <template>
  2. <div class="header-container">
  3. <div class="wrapper">
  4. <!-- logo -->
  5. <div class="left-wrapper v-link selected">
  6. <img
  7. style= "width: 50px"
  8. width= "50"
  9. height= "50"
  10. src= "~assets/images/logo.png"
  11. />
  12. <span class="text">尚医通 预约挂号统一平台 </span>
  13. </div>
  14. <!-- 搜索框 -->
  15. <div class="search-wrapper">
  16. <div class="hospital-search animation-show">
  17. <el-autocomplete
  18. class= "search-input small"
  19. prefix-icon= "el-icon-search"
  20. v-model= "hosname"
  21. :fetch-suggestions= "querySearchAsync"
  22. placeholder= "点击输入医院名称"
  23. @ select= "handleSelect"
  24. >
  25. <span
  26. slot= "suffix"
  27. class= "search-btn v-link highlight clickable selected"
  28. >搜索
  29. </span>
  30. </el-autocomplete>
  31. </div>
  32. </div>
  33. <!-- 右侧 -->
  34. <!-- 右侧 -->
  35. <div class="right-wrapper">
  36. <span class="v-link clickable">帮助中心 </span>
  37. <span
  38. v-if= "name == ''"
  39. class= "v-link clickable"
  40. @ click= "showLogin()"
  41. id= "loginDialog"
  42. >登录/注册</span
  43. >
  44. <el-dropdown v-if="name != ''" @command="loginMenu">
  45. <span class="el-dropdown-link">
  46. {{ name }} <i class="el-icon-arrow-down el-icon--right"></i>
  47. </span>
  48. <el-dropdown-menu class="user-name-wrapper" slot="dropdown">
  49. <el-dropdown-item command="/user">实名认证 </el-dropdown-item>
  50. <el-dropdown-item command="/order">挂号订单 </el-dropdown-item>
  51. <el-dropdown-item command="/patient">就诊人管理 </el-dropdown-item>
  52. <el-dropdown-item command="/logout" divided
  53. >退出登录</el-dropdown-item
  54. >
  55. </el-dropdown-menu>
  56. </el-dropdown>
  57. </div>
  58. </div>
  59. <!-- 登录弹出层 -->
  60. <el-dialog
  61. :visible.sync= "dialogUserFormVisible"
  62. style= "text-align: left; display: none"
  63. top= "50px"
  64. :append-to-body= "true"
  65. width= "960px"
  66. @ close= "closeDialog()"
  67. >
  68. <div class="container">
  69. <!-- 手机登录 #start -->
  70. <div class="operate-view" v-if="dialogAtrr.showLoginType === 'phone'">
  71. <div class="wrapper" style="width: 100%">
  72. <div class="mobile-wrapper" style="position: static; width: 70%">
  73. <span class="title"> {{ dialogAtrr.labelTips }} </span>
  74. <el-form>
  75. <el-form-item>
  76. <el-input
  77. v-model= "dialogAtrr.inputValue"
  78. :placeholder= "dialogAtrr.placeholder"
  79. :maxlength= "dialogAtrr.maxlength"
  80. class= "input v-input"
  81. >
  82. <span
  83. slot= "suffix"
  84. class= "sendText v-link"
  85. v-if= "dialogAtrr.second > 0"
  86. > {{ dialogAtrr.second }} s
  87. </span>
  88. <span
  89. slot= "suffix"
  90. class= "sendText v-link highlight clickable selected"
  91. v-if= "dialogAtrr.second == 0"
  92. @ click= "getCodeFun()"
  93. >重新发送
  94. </span>
  95. </el-input>
  96. </el-form-item>
  97. </el-form>
  98. <div class="send-button v-button" @click="btnClick()">
  99. {{ dialogAtrr.loginBtn }}
  100. </div>
  101. </div>
  102. <div class="bottom">
  103. <div class="wechat-wrapper" @click="weixinLogin()">
  104. <span class="iconfont icon"></span>
  105. </div>
  106. <span class="third-text"> 第三方账号登录 </span>
  107. </div>
  108. </div>
  109. </div>
  110. <!-- 手机登录 #end -->
  111. <!-- 微信登录 #start -->
  112. <div class="operate-view" v-if="dialogAtrr.showLoginType === 'weixin'">
  113. <div class="wrapper wechat" style="height: 400px">
  114. <div>
  115. <div id="weixinLogin"> </div>
  116. </div>
  117. <div class="bottom wechat" style="margin-top: -80px">
  118. <div class="phone-container">
  119. <div class="phone-wrapper" @click="phoneLogin()">
  120. <span class="iconfont icon"></span>
  121. </div>
  122. <span class="third-text"> 手机短信验证码登录 </span>
  123. </div>
  124. </div>
  125. </div>
  126. </div>
  127. <!-- 微信登录 #end -->
  128. <div class="info-wrapper">
  129. <div class="code-wrapper">
  130. <div>
  131. <img
  132. src= "//img.114yygh.com/static/web/code_login_wechat.png"
  133. class= "code-img"
  134. />
  135. <div class="code-text">
  136. <span class="iconfont icon"></span>微信扫一扫关注
  137. </div>
  138. <div class="code-text">“快速预约挂号” </div>
  139. </div>
  140. <div class="wechat-code-wrapper">
  141. <img
  142. src= "//img.114yygh.com/static/web/code_app.png"
  143. class= "code-img"
  144. />
  145. <div class="code-text">扫一扫下载 </div>
  146. <div class="code-text">“预约挂号”APP </div>
  147. </div>
  148. </div>
  149. <div class="slogan">
  150. <div>xxxxxx官方指定平台 </div>
  151. <div>快速挂号 安全放心 </div>
  152. </div>
  153. </div>
  154. </div>
  155. </el-dialog>
  156. <div> </div>
  157. <!--解决登录框样式不对-->
  158. </div>
  159. </template>
  160. <script>
  161. import cookie from "js-cookie";
  162. import Vue from "vue";
  163. import userInfoApi from "@/api/userInfo";
  164. import smsApi from "@/api/msm";
  165. import hospitalApi from "@/api/hosp";
  166. import weixinApi from "@/api/weixin";
  167. const defaultDialogAtrr = {
  168. showLoginType: "phone", // 控制手机登录与微信登录切换
  169. labelTips: "手机号码", // 输入框提示
  170. inputValue: "", // 输入框绑定对象
  171. placeholder: "请输入您的手机号", // 输入框placeholder
  172. maxlength: 11, // 输入框长度控制
  173. loginBtn: "获取验证码", // 登录按钮或获取验证码按钮文本
  174. sending: true, // 是否可以发送验证码
  175. second: - 1, // 倒计时间 second>0 : 显示倒计时 second=0 :重新发送 second=-1 :什么都不显示
  176. clearSmsTime: null, // 倒计时定时任务引用 关闭登录层清除定时任务
  177. };
  178. export default {
  179. data( ) {
  180. return {
  181. userInfo: {
  182. phone: "",
  183. code: "",
  184. openid: "",
  185. hosname: "",
  186. },
  187. dialogUserFormVisible: false,
  188. // 弹出层相关属性
  189. dialogAtrr: defaultDialogAtrr,
  190. name: "", // 用户登录显示的名称
  191. };
  192. },
  193. created( ) {
  194. this. showInfo();
  195. },
  196. mounted( ) {
  197. //在页面渲染之后执行的方法
  198. // 注册全局登录事件对象
  199. window. loginEvent = new Vue();
  200. // 监听登录事件
  201. loginEvent.$on( "loginDialogEvent", function ( ) {
  202. document. getElementById( "loginDialog"). click();
  203. });
  204. // 触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
  205. //初始化微信js body下面追加一个文件
  206. const script = document. createElement( "script");
  207. script. type = "text/javascript";
  208. script. src =
  209. "https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js";
  210. document. body. appendChild(script);
  211. // 微信登录回调处理
  212. let self = this;
  213. window[ "loginCallback"] = (name, token, openid) => {
  214. self. loginCallback(name, token, openid);
  215. };
  216. },
  217. methods: {
  218. //微信回调方法
  219. loginCallback( name, token, openid) {
  220. // 打开手机登录层,绑定手机号,改逻辑与手机登录一致
  221. if (openid != null || openid != "") {
  222. this. userInfo. openid = openid;
  223. this. showLogin();
  224. } else {
  225. this. setCookies(name, token);
  226. }
  227. },
  228. // 绑定登录或获取验证码按钮
  229. btnClick( ) {
  230. // 判断是获取验证码还是登录
  231. if ( this. dialogAtrr. loginBtn == "获取验证码") {
  232. this. userInfo. phone = this. dialogAtrr. inputValue;
  233. // 获取验证码
  234. this. getCodeFun();
  235. } else {
  236. // 登录
  237. this. login();
  238. }
  239. },
  240. // 绑定登录,点击显示登录层
  241. showLogin( ) {
  242. this. dialogUserFormVisible = true;
  243. // 初始化登录层相关参数
  244. this. dialogAtrr = { ...defaultDialogAtrr };
  245. },
  246. // 登录
  247. login( ) {
  248. this. userInfo. code = this. dialogAtrr. inputValue;
  249. if ( this. dialogAtrr. loginBtn == "正在提交...") {
  250. this. $message. error( "重复提交");
  251. return;
  252. }
  253. if ( this. userInfo. code == "") {
  254. this. $message. error( "验证码必须输入");
  255. return;
  256. }
  257. if ( this. userInfo. code. length != 6) {
  258. this. $message. error( "验证码格式不正确");
  259. return;
  260. }
  261. this. dialogAtrr. loginBtn = "正在提交...";
  262. userInfoApi
  263. . login( this. userInfo)
  264. . then( (response) => {
  265. console. log(response. data);
  266. // 登录成功 设置cookie
  267. this. setCookies(response. data. name, response. data. token);
  268. })
  269. . catch( (e) => {
  270. this. dialogAtrr. loginBtn = "马上登录";
  271. });
  272. },
  273. setCookies( name, token) {
  274. cookie. set( "token", token, { domain: "localhost" });
  275. cookie. set( "name", name, { domain: "localhost" });
  276. window. location. reload();
  277. },
  278. // 获取验证码
  279. getCodeFun( ) {
  280. if (! /^1[34578]\d{9}$/. test( this. userInfo. phone)) {
  281. this. $message. error( "手机号码不正确");
  282. return;
  283. }
  284. // 初始化验证码相关属性
  285. this. dialogAtrr. inputValue = "";
  286. this. dialogAtrr. placeholder = "请输入验证码";
  287. this. dialogAtrr. maxlength = 6;
  288. this. dialogAtrr. loginBtn = "马上登录";
  289. // 控制重复发送
  290. if (! this. dialogAtrr. sending) return;
  291. // 发送短信验证码
  292. this. timeDown();
  293. this. dialogAtrr. sending = false;
  294. smsApi
  295. . sendCode( this. userInfo. phone)
  296. . then( (response) => {
  297. this. timeDown();
  298. })
  299. . catch( (e) => {
  300. this. $message. error( "发送失败,重新发送");
  301. // 发送失败,回到重新获取验证码界面
  302. this. showLogin();
  303. });
  304. },
  305. // 倒计时
  306. timeDown( ) {
  307. if ( this. clearSmsTime) {
  308. clearInterval( this. clearSmsTime);
  309. }
  310. this. dialogAtrr. second = 60;
  311. this. dialogAtrr. labelTips = "验证码已发送至" + this. userInfo. phone;
  312. this. clearSmsTime = setInterval( () => {
  313. -- this. dialogAtrr. second;
  314. if ( this. dialogAtrr. second < 1) {
  315. clearInterval( this. clearSmsTime);
  316. this. dialogAtrr. sending = true;
  317. this. dialogAtrr. second = 0;
  318. }
  319. }, 1000);
  320. },
  321. // 关闭登录层
  322. closeDialog( ) {
  323. if ( this. clearSmsTime) {
  324. clearInterval( this. clearSmsTime);
  325. }
  326. },
  327. showInfo( ) {
  328. let token = cookie. get( "token");
  329. if (token) {
  330. this. name = cookie. get( "name");
  331. console. log( this. name);
  332. }
  333. },
  334. loginMenu( command) {
  335. if ( "/logout" == command) {
  336. cookie. set( "name", "", { domain: "localhost" });
  337. cookie. set( "token", "", { domain: "localhost" });
  338. //跳转页面
  339. window. location. href = "/";
  340. } else {
  341. window. location. href = command;
  342. }
  343. },
  344. handleSelect( item) {
  345. window. location. href = "/hospital/" + item. hoscode;
  346. },
  347. weixinLogin( ) {
  348. this. dialogAtrr. showLoginType = "weixin";
  349. //初始化微信的相关参数
  350. weixinApi. getLoginParam(). then( (response) => {
  351. var obj = new WxLogin({
  352. self_redirect: true,
  353. id: "weixinLogin", // 需要显示的容器id
  354. appid: response. data. appid, // 公众号appid wx*******
  355. scope: response. data. scope, // 网页默认即可
  356. redirect_uri: response. data. redirect_Uri, // 授权成功后回调的url
  357. state: response. data. state, // 可设置为简单的随机数加session用来校验
  358. style: "black", // 提供"black"、"white"可选。二维码的样式
  359. href: "", // 外部css文件url,需要https
  360. });
  361. });
  362. },
  363. phoneLogin( ) {
  364. this. dialogAtrr. showLoginType = "phone";
  365. this. showLogin();
  366. },
  367. },
  368. };
  369. </script>

 然后修改一下手机登录的部分,我们把微信登录跟手机号绑定写到一起,微信扫描之后还是进入手机的登录页面

修改UserInfoServiceImpl:的手机登录


  
  1. //用户手机登录接口
  2. @Override
  3. public Map< String, Object> login( LoginVo loginVo) {
  4. //从loginVo里面获取输入的手机号和验证码
  5. String phone = loginVo. getPhone();
  6. String code = loginVo. getCode();
  7. //校验参数 判断手机号和验证码是否为空
  8. if( StringUtils. isEmpty(phone) || StringUtils. isEmpty(code)) {
  9. throw new HospitalException( ResultCodeEnum. PARAM_ERROR);
  10. }
  11. //(整合阿里云短信服务)判断手机验证码和输入的验证码是否一致
  12. //校验校验验证码
  13. String mobleCode = redisTemplate. opsForValue(). get(phone);
  14. if(!code. equals(mobleCode)) {
  15. throw new HospitalException( ResultCodeEnum. CODE_ERROR);
  16. }
  17. //绑定手机号码
  18. UserInfo userInfo = null;
  19. if(! StringUtils. isEmpty(loginVo. getOpenid())) {
  20. userInfo = this. selectWxInfoOpenId(loginVo. getOpenid());
  21. if( null != userInfo) {
  22. userInfo. setPhone(loginVo. getPhone());
  23. this. updateById(userInfo);
  24. } else {
  25. throw new HospitalException( ResultCodeEnum. DATA_ERROR);
  26. }
  27. }
  28. //如果userInf为空做的是正常的手机登录
  29. if (userInfo== null){
  30. //判断是否是第一次登录:根据手机号查询数据库,如果不存在相同的手机号就是第一次登录
  31. QueryWrapper< UserInfo> queryWrapper = new QueryWrapper<>();
  32. queryWrapper. eq( "phone", phone);
  33. userInfo = baseMapper. selectOne(queryWrapper);
  34. if( null == userInfo) { //等于空表示第一次使用这个手机号登录
  35. userInfo = new UserInfo();
  36. userInfo. setName( "");
  37. userInfo. setPhone(phone);
  38. userInfo. setStatus( 1);
  39. baseMapper. insert(userInfo);
  40. }
  41. }
  42. //校验是否被禁用
  43. if(userInfo. getStatus() == 0) { //判断用户状态是否为0禁用状态
  44. throw new HospitalException( ResultCodeEnum. LOGIN_DISABLED_ERROR);
  45. }
  46. //不是第一次登录,直接登陆
  47. //返回登录的信息 :返回用户名 返回tocken信息
  48. Map< String, Object> map = new HashMap<>();
  49. String name = userInfo. getName();
  50. if( StringUtils. isEmpty(name)) {
  51. name = userInfo. getNickName();
  52. }
  53. if( StringUtils. isEmpty(name)) {
  54. name = userInfo. getPhone();
  55. }
  56. map. put( "name", name);
  57. //JWT工具类生成token
  58. String token = JwtHelper. createToken(userInfo. getId(), name);
  59. map. put( "token", token);
  60. return map;
  61. }

 

点击登录:

点击第三方账号登录:

用手机扫描二维码:

 

 yygh_user数据库表user_nfo成功插入一条数据:

 

 

 然后显示绑定手机号:

 

这页面换成了昵称显示登录:

 


转载:https://blog.csdn.net/dengfengling999/article/details/128758669
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场