java-uniapp小程序-引导关注公众号、判断用户是否关注公众号

目录

1、前期准备

公众号和小程序相互关联

准备公众号文章

注册公众号测试号

微信静默授权的独立html 文件

2: 小程序代码

webview页面代码

小程序首页代码

3:后端代码

1:增加公众号配置项

2:读取公众号配置项

3:增加微信公众号工具类

4:增加微信用户基本信息类

5:增加跳转html页面的接口

6:HttpClientUtil网络请求工具类

7:使用步骤

4:上线配置


这篇文章讲的是uniapp微信小程序【引导用户关注微信公众号】,【判断用户是否关注公众号】,用的是后端是 java springBoot,不是自己想要的网友可以关闭页面了。


需求背景:客户的下单系统原本是建立在微信公众号的,现在要求换成小程序,但涉及到消息推送的还是用公众号的推送,所以要求用户进入小程序的时候判断是否已经关注公众号了,没有关注就提示,并引导用户关注。


一收到需求,鸭蛋,没弄过,纯纯小白,第一件事当然就是找度娘了,查出的资料五花八门,各种各样,各种不完整,缺失关键代码和步骤,尤其【C】SDN,一大堆都是抄的,真TM的痛苦,没办法,我太菜了。


现在说一下我的实现方式:

引导关注公众号:微信小程序有个自带的引导用户关注微信公众号的组件,official-account,使用简单,就是限制太多了,必须指定方式打开小程序才显示,完全不符合客户需求,后来我是跳转到公众号的一篇文章,里面有公众号卡片和二维码,让用户进行关注。

判断是否关注公众号:网上基本清一色就是建表存关注的用户信息,然后定时拉取更新表。还有就是主动拉取微信公众号全部关注的用户,然后通过unionid进行判断是否关注。我一看就觉得完全没法用,公众号的用户体量几千万,要是每次判断都是去拉取全部用户信息,然后再一个个对比,这时间得多久,性能也会吃力。其实来来回回主要的难点就是在于小程序怎么获取到用户在公众号的openId,我的做法是跳转一个空白登录页,获取到微信公众的loginCode后,后台解析获取到对应的openId,再用公众号的openId去判断是否已经关注,然后再跳转回对应功能页面,对于用户来说,就是多了一次跳转,时间也就是1秒多这样,当然不是在首页进行操作,具体可以认真看完。


1、前期准备


 1:一个微信公众号,一个微信小程序,以及他们的appid和AppSecret(怎么拿到自己百度吧,也不是很难)。

2:微信公众号的一篇引导关注文章。

3:一个独立的html文件,里面是写微信公众号静默登录代码的。

4:在微信公众号后台管理那里,注册一个测试号,用作等会静默登录测试用。

5:把自己的微信号都添加成公众号和小程序的开发者。


搜索微信公众平台,扫码登录选择公众号,进入公众号管理后台 

在微信管理后台把自己的微信号加入到公众号的开发者里面。

小程序如果你能登录到管理后台,说明你已经是项目成员,不然让管理员把你加到项目成员里面就可以了。 


公众号和小程序相互关联

首先是小程序和公众号必须是同一个主体下的,并相互关联,如果不相互关联,小程序的web-view组件是打不开公众号的文章的,比如出现下面这种情况。

官方文档链接:小程序webview访问公众号文章提示非业务域名 | 微信开放社区


 》》搜索微信公众平台,扫码登录选择公众号,进入公众号管理后台,进行关联小程序。


》》搜索微信公众平台,扫码登录选择小程序,进入小程序管理后台,左下角设置,进行关联公众号。


准备公众号文章

微信公众号发一篇引导关注文章,放个二维码,公众号卡片啥的,然后去复制这篇文章的链接,这一步看似简单,实则有坑,按常规的步骤复制,得到是一个短链接,比如下面的步骤得出链接是:https://mp.weixin.qq.com/s/7wYtVJc6XXXXXXXXXXXX...

这样的链接你在微信开发者工具是完全没有问题的,一到真机上面就不行。 

我们必须复制出一个长链接,有参数的。首先电脑端微信打开公众号主页,找到对应文章,右键选择默认浏览器打开,然后再复制浏览器上的链接,就会得到一个长链接,比如:

https://mp.weixin.qq.com/s?__biz=MjM5&mid=28192&idx=1....

后面带有_biz,mid这种参数的

拿到引导关注的文章链接,先找个地方保存好,后面要用。


注册公众号测试号

接下来是注册一个测试公众号,搜索微信公众平台,扫码登录选择公众号,进入公众号管理后台。

里面步骤【记下测试号的appid和appsecret】【JS接口安全域名】【自己微信扫码关注测试号】【体验接口权限表里面找到网页授权获取用户基本信息,右边修改授权回调页面域名】

两个域名也可以保持一致。

测试号的域名可以直接填本机ip加端口号,记住不要加http或https开头,比如 192.168.0.41:8080


微信静默授权的独立html 文件

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width" /><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /></head><body></body><!-- 引入微信js库 --><script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script><script>/*** 代码说明:* 1:进入这个页面后,执行window.onload方法,然后调用getUrlCode方法判断有没有code参数*    没有code参数,重定向到微信授权链接,此链接无感静默授权,完成后会调指定的回调链接,也就是REDIRECT_URI参数那里。*    如果有code参数,说明是授权完成,回调后再次进入这个页面的,此时的code应该就是wx.login方法获得的code。* 2:获得code后,wx.miniProgram.redirectTo关闭当前页面,跳转到小程序里面的webview组件页面,/pages/webView/webView页面里面只有一个webview组件,*    我们传入url和微信登录code参数进行跳转,此时webview组件打开公众号文章,并获得了此用户在公众号的登录code。**/window.onload = ()=>{const { code, state } = getUrlCode()if (code) {const subscribeUrl = `url=${encodeURIComponent('前面准备的公众号文章链接')}&title=${decodeURIComponent('小程序webView页面的标题,此参数不是必须')}&wxPublicLoginCode=${encodeURIComponent(code)}`wx.miniProgram.redirectTo({url: `/pages/webView/webView?${subscribeUrl}`})} else {window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect'.replace('APPID', '你的公众号appid,现在开发先用测试号的appid').replace('REDIRECT_URI', '微信授权完后的回调页面链接,别着急,慢慢看完文章').replace('STATE', '0') // 进入回调链接后的附带参数,看个人需要吧,有需要就传,比如你这个页面被多个业务场景使用,但每个业务场景回调的页面不一样,至于怎么用,动动脑子}}// 截取url中的code方法function getUrlCode() {const url = location.searchconst theRequest = new Object()if (url.indexOf("?") != -1) {const str = url.substr(1)const strs = str.split("&")for (let i = 0; i < strs.length; i++) {theRequest[strs[i].split("=")[0]] = strs[i].split("=")[1]}}return theRequest}</script>
</html>

具体思路是:用户通过webview组件访问我们写的html页面,首次进入,页面会重定向到微信授权,授权完成后,再次重定向回此html页面,此页面拿到code,然后关闭此html页面,跳转微信公众号文章,用户进行关注,然后用户点击返回的时候,我们通过拿到的code去获取微信公众号的openId,进而通过openId判断用户是否已关注。流程图大致如下吧。

如果要了解微信网页授权,也就是html页面里面的window.location.href重定向授权链接参数,可以看下面的文档,这里不详细说明了。

官方的网页授权文档:网页授权 | 微信开放文档 


2: 小程序代码


我这边的做法是用户表里面有个字段记录微信公众号的openId,用户登录进入首页后,如果此字段没有值,则说明此账号没有绑定过微信公众号。这里可能有人会说,如果这个账号在其他微信号上面登录呢,也就是在A微信登录,并关注公众号,然后又去B微信登录,此时表里的字段存的是A微信的openId,B微信实则没有关注,这里其实就要看对应系统业务需要了,我这边是要关注公众号后,然后利用公众号推消息,所以我的小程序里面会有一个页面是用来专门处理换绑,也就是从A微信的关注换绑到B微信的关注,但现在写教程,我就不说具体业务了,只说怎么拿到openId,然后判断有没有关注,如果你是需要每次都判断当前微信有没有关注,那把具体入口写在登录页吧,比如用户点击登录跳转登录页,你可能是跳转/pages/login页面,那改成直接跳转webview页面,然后访问我们写的html授权页面,授权完成拿到code后,html页面代码里面的wx.miniProgram.redirectTo跳转到小程序登录页去,那你小程序的登录页也就拿到code了,从而可以判断是否已经关注。

回归正题

我们登录成功进入首页,判断openId如果没有值,则则给出关注提示。点击提示跳转到webview组件页面,访问我们写的html页面,一连串执行后,用户点击返回会回到首页,首页接收微信公众号的登录code,并调接口进行解析。如果有openId,我们也可以发出请求,查此openId的微信号有没有关注公众号。


webview页面代码

记得在pages文件配置上这个页面

<template><view><web-view :src="url"></web-view></view>
</template><script>export default {data() {return {url: '',wxPublicLoginCode: void (0)}},onLoad(options) {// 获取到要访问的链接,并decodeURIComponent解码this.url = decodeURIComponent(options.url)// webview页面标题if (options.title) {uni.setNavigationBarTitle({title: decodeURIComponent(options.title)})}// 微信公众号登录codethis.wxPublicLoginCode = decodeURIComponent(options.wxPublicLoginCode || '')},// 页面卸载事件, 用户在微信公众号文章页面点击返回触发onUnload () {const pages = getCurrentPages()if (pages.length > 1) {const { route } = pages[pages.length - 2]// 判断路由栈里面当前页面的上一个页面是不是首页,如果是则设置缓存if (route === 'pages/index/index') {this.wxPublicLoginCode && uni.setStorageSync('wxPublicLoginCode', this.wxPublicLoginCode)}}}}
</script>

小程序首页代码

1:判断登录后有表里没有微信公众号的openId,如果有onLoad方法请求后端,获取微信是否关注公众号,如果没有,显示提示。如果表里没有微信公众号的openId,直接显示提示。

2:点击提示跳转webview页面。此链接打开的是我们写的那个html页面,本地写成自己的ip,上线后用线上域名。后端部分后面再说。

注意:http://192.168.0.41:8080/api/express/member/wxPublicLogin是访问我们写的html页面的链接,那么前面我们写的html文件里面,REDIRECT_URI的值也是这个链接。

uni.navigateTo({url: `/pages/webView/webView?url=${encodeURIComponent('http://192.168.0.41:8080/api/express/member/wxPublicLogin')}`
})

3:跳转webview页面后,一系列执行,进入公众号文章页面。关不关注那是用户的事,我们只负责引导。

4:点击左上角返回,会设置缓存code,然后回到首页。code是在webview页面的onUnload方法设置的。首页的onShow方法写上接收缓存。

onShow () {const wxPublicLoginCode = uni.getStorageSync('wxPublicLoginCode')if (wxPublicLoginCode) {uni.removeStorageSync('wxPublicLoginCode')this.onBindingWxPublic(wxPublicLoginCode)}
}

onBindingWxPublic方法是把code传回后端,然后解析出openId,然后查询是否已关注公众号,关注了就记录openId到表里面,没有关注就不管了,接口返回 boolean 是否已关注就行了。


3:后端代码

1:增加公众号配置项

#微信公众号的appid,开发阶段就填测试号的,上线再填真正公众号的
wx.public.appid=xxxxx549xxxxxxxxx
#微信公众号的appsecret,开发阶段就填测试号的,上线再填真正公众号的
wx.public.secret=xxxxxxxf60xx7bc0xxxxxxxxxxxxxx
#这是我们写的html页面位置,本地开发就先写死,上线后把文件放到服务器上面,再把这里的配置路径改掉
wx.public.loginFilePath=C:/Users/user1/Desktop/wx_public_login.html

2:读取公众号配置项

建一个WxPublicProperties类

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;@Configuration
@ConfigurationProperties(prefix = "wx.public")
public class WxPublicProperties {/*** 微信公众号的appid*/private String appid;/*** 微信公众号的Secret*/private String secret;/*** 微信公众号的登录文件路径*/private String loginFilePath;public String getAppid() {return appid;}public void setAppid(String appid) {this.appid = appid;}public String getSecret() {return secret;}public void setSecret(String secret) {this.secret = secret;}public String getLoginFilePath() {return loginFilePath;}public void setLoginFilePath(String loginFilePath) {this.loginFilePath = loginFilePath;}
}

3:增加微信公众号工具类

类名:WxPublicUtil,里面的HttpClientUtil类放在最后面

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Objects;@Component
public class WxPublicUtil {private final static Logger log = LoggerFactory.getLogger(WxMiniAppUtil.class);private WxPublicProperties wxPublicProperties;/*** 获取用户微信公众号openId* @param wxPublicLoginCode 微信公众号登录code* @return 用户公众号openId*/public String getOpenId(String wxPublicLoginCode) {try {// 拼接url发起请求到微信认证服务器获取access_token等信息String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=LOGIN_CODE&grant_type=authorization_code".replace("APPID", this.wxPublicProperties.getAppid()).replace("APPSECRET", this.wxPublicProperties.getSecret()).replace("LOGIN_CODE", wxPublicLoginCode);String accessTokenResult = HttpClientUtil.doGet(tokenUrl);log.info("获取微信公众号open_id请求结果如下,loginCode:{},requestResult:{}", wxPublicLoginCode, accessTokenResult);if (Objects.isNull(accessTokenResult)) {return null;}JSONObject tokenObj = JSONObject.parseObject(accessTokenResult);if (StringUtils.isNotBlank(tokenObj.getString("errcode"))) {return null;}return tokenObj.getString("openid");} catch (Exception e) {log.error("获取微信公众号open_id失败", e);return null;}}/*** 获取用户基本信息* @param openId 微信公众号openId**/public WxUserInfoVO getUserBaseInfo(String openId) {if (StringUtils.isBlank(openId)) {return null;}String accessToken = this.getAccessToken();if (StringUtils.isBlank(accessToken)) {return null;}try {String requestUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN".replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);String requestResult = HttpClientUtil.doGet(requestUrl);log.info("获取微信用户基本信息请求结果如下: {}", requestResult);if (Objects.isNull(requestResult)) {return null;}JSONObject resultObject = JSONObject.parseObject(requestResult);if (StringUtils.isNotBlank(resultObject.getString("errcode"))) {return null;}return JSONObject.parseObject(requestResult, WxUserInfoVO.class);} catch (Exception e) {log.error("获取微信公众号access_token失败", e);return null;}}/*** 获取access_token*/private String getAccessToken() {try {String requestUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET".replace("APPID", this.wxPublicProperties.getAppid()).replace("APPSECRET", this.wxPublicProperties.getSecret());String accessTokenResult = HttpClientUtil.doGet(requestUrl);log.info("获取微信公众号access_token请求结果如下: {}", accessTokenResult);if (Objects.isNull(accessTokenResult)) {return null;}JSONObject tokenObj = JSONObject.parseObject(accessTokenResult);if (StringUtils.isNotBlank(tokenObj.getString("errcode"))) {return null;}return tokenObj.getString("access_token");} catch (Exception e) {log.error("获取微信公众号access_token失败", e);return null;}}@Autowiredpublic void setWxPublicProperties(WxPublicProperties wxPublicProperties) {this.wxPublicProperties = wxPublicProperties;}
}

4:增加微信用户基本信息类

类名:WxUserInfoVO,里面有个微信公众号最近一次关注的时间subscribeTime,那个时间戳要转换成long再乘以1000才是完整时间,有需要这个时间的话,注意一下

get和set方法我去掉了,自己加一下吧。

import java.io.Serializable;/*** 微信用户信息*/
public class WxUserInfoVO implements Serializable {private static final long serialVersionUID = 1L;/*** openId*/private String openid;/*** 昵称*/private String nickname;/*** 性别*/private String sex;/*** 语言*/private String language;/*** 省份*/private String province;/*** 城市*/private String city;/*** 区县*/private String country;/*** 头像*/private String headimgurl;/*** 是否已关注*/private Integer subscribe;/*** 关注时间*/private String subscribeTime;/*** unionid*/private String unionid;
}

5:增加跳转html页面的接口

记得先用@Autowired注入WxPublicProperties,这个接口是读取我们写的html页面,并返回,相当于是用户调用这个接口是进入我们写的html页面。IOUtils.toByteArray是把输入流转成字节,如果没有这个工具类,就手写吧,实在不懂百度【java输入流转byte字节数组】。

这个接口记得不要被权限拦截,也就是没有登录也可以访问。

/*** 公众号无感登录*/
@RequestMapping(value = "/member/wxPublicLogin", method = RequestMethod.GET)
public void wxPublicLogin(HttpServletResponse response) throws IOException {try(FileInputStream fileInputStream = new FileInputStream(this.wxPublicProperties.getLoginFilePath());){response.getOutputStream().write(IOUtils.toByteArray(fileInputStream));}
}

6:HttpClientUtil网络请求工具类

import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;public class HttpClientUtil {private final static Logger log = LoggerFactory.getLogger(HttpClientUtil.class);/*** 连接池最大连接数*/private static final int MAX_TOTAL_CONNECTIONS = 200;/*** 设置每个路由上的默认连接个数*/private static final int DEFAULT_MAX_PER_ROUTE = 20;/*** 请求的请求超时时间 单位:毫秒*/private static final int REQUEST_CONNECTION_TIMEOUT = 8 * 1000;/*** 请求的等待数据超时时间 单位:毫秒*/private static final int REQUEST_SOCKET_TIMEOUT = 8 * 1000;/*** 请求的连接超时时间 单位:毫秒*/private static final int REQUEST_CONNECTION_REQUEST_TIMEOUT = 5 * 1000;/*** 连接闲置多久后需要重新检测 单位:毫秒*/private static final int VALIDATE_AFTER_IN_ACTIVITY = 2 * 1000;/*** 关闭Socket时,要么发送完所有数据,要么等待多少秒后,就关闭连接,此时socket.close()是阻塞的 单位秒*/
//	private static final int SOCKET_CONFIG_SO_LINGER = 60;/*** 接收数据的等待超时时间,即读超时时间,单位ms*/private static final int SOCKET_CONFIG_SO_TIMEOUT = 5 * 1000;/*** 重试次数*/private static int RETRY_COUNT = 5;/*** 声明为 static volatile,会迫使线程每次读取时作为一个全局变量读取*/private static volatile CloseableHttpClient httpClient = null;/*** @param uri* @return String* @description get请求方式* @author: long.he01*/public static String doGet(String uri) {String responseBody;HttpGet httpGet = new HttpGet(uri);try {httpGet.setConfig(getRequestConfig());responseBody = executeRequest(httpGet);} catch (IOException e) {log.error("httpclient doGet方法异常, 请求地址:{}", uri);throw new RuntimeException("httpclient doGet方法异常 ", e);} finally {httpGet.releaseConnection();}return responseBody;}/*** @return RequestConfig* @description: 获得请求配置信息*/private static RequestConfig getRequestConfig() {RequestConfig defaultRequestConfig = RequestConfig.custom().setExpectContinueEnabled(true).build();return RequestConfig.copy(defaultRequestConfig).setSocketTimeout(REQUEST_CONNECTION_TIMEOUT).setConnectTimeout(REQUEST_SOCKET_TIMEOUT).setConnectionRequestTimeout(REQUEST_CONNECTION_REQUEST_TIMEOUT).build();}/*** @param method* @return String* @throws IOException* @description 通用执行请求方法*/private static String executeRequest(HttpUriRequest method) throws IOException {ResponseHandler<String> responseHandler = new ResponseHandler<String>() {@Overridepublic String handleResponse(final HttpResponse response) throws IOException {int status = response.getStatusLine().getStatusCode();String result;if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {HttpEntity entity = response.getEntity();result = entity != null ? EntityUtils.toString(entity) : null;EntityUtils.consume(entity);return result;} else {throw new ClientProtocolException("Unexpected response status: " + status);}}};String result = getHttpClientInstance().execute(method, responseHandler);return result;}/*** @return CloseableHttpClient* @description 单例获取httpclient实例*/private static CloseableHttpClient getHttpClientInstance() {if (httpClient == null) {synchronized (CloseableHttpClient.class) {if (httpClient != null) {return httpClient;}ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {@Overridepublic long getKeepAliveDuration(HttpResponse response, HttpContext context) {HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));while (it.hasNext()) {HeaderElement he = it.nextElement();String param = he.getName();String value = he.getValue();if (value != null && param.equalsIgnoreCase("timeout")) {return Long.parseLong(value) * 1000;}}return 60 * 1000;// 如果没有约定,则默认定义时长为60s}};httpClient = HttpClients.custom().setConnectionManager(initConfig()).setKeepAliveStrategy(myStrategy).setRetryHandler(getRetryHandler()).build();}}return httpClient;}/*** @return HttpRequestRetryHandler* @description :获取重试handler*/private static HttpRequestRetryHandler getRetryHandler() {// 请求重试处理return new HttpRequestRetryHandler() {@Overridepublic boolean retryRequest(IOException exception, int executionCount, HttpContext context) {if (executionCount >= RETRY_COUNT) {// 假设已经重试了5次,就放弃return false;}if (exception instanceof NoHttpResponseException) {// 假设server丢掉了连接。那么就重试return true;}if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常return false;}if (exception instanceof InterruptedIOException) {// 超时return false;}if (exception instanceof UnknownHostException) {// 目标server不可达return false;}if (exception instanceof ConnectTimeoutException) {// 连接被拒绝return false;}if (exception instanceof SSLException) {// SSL握手异常return false;}HttpRequest request = HttpClientContext.adapt(context).getRequest();// 假设请求是幂等的,就再次尝试return !(request instanceof HttpEntityEnclosingRequest);}};}/*** @return PoolingHttpClientConnectionManager* @description 初始化连接池等配置信息*/private static PoolingHttpClientConnectionManager initConfig() {Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE).register("https", new SSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build();PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).setSoReuseAddress(true).setSoTimeout(SOCKET_CONFIG_SO_TIMEOUT).setSoKeepAlive(true).build();connManager.setDefaultSocketConfig(socketConfig);connManager.setValidateAfterInactivity(VALIDATE_AFTER_IN_ACTIVITY);ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Consts.UTF_8).build();connManager.setDefaultConnectionConfig(connectionConfig);connManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);connManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);return connManager;}}

7:使用步骤

首先前端是调/member/wxPublicLogin接口进入html页面,然后html页面拿到code返回到具体功能页,这个时候就看你业务了,公众号的登录code都拿到了,你想怎么干就怎么干了。

用code换取openId,并用openId获取用户基本信息。记得先@Autowired注入WxPublicUtil,userBaseInfo里面就有openId等关注信息。字段subscribe = 1 就是已关注,0就是未关注。

WxUserInfoVO userBaseInfo = this.wxPublicUtil.getUserBaseInfo(this.wxPublicUtil.getOpenId('这里是获取到的code'));

4:上线配置

1:配置文件那里,appid,secret等都要换成正式公众号的,loginFilePath换成服务器或者其他容器存放的地址。


2:登录公众号的管理后台,填写网页授权域名,填正式环境的域名,它会给你一个文本文件让你放在域名根目录,照做就行了。


3:微信公众号增加获取access_token接口ip白名单,把服务器的ip加上去。

如果获取access_token出现这个错误就是ip白名单没有配置 。复制错误信息里面ip去配置就行了。

{"errcode":40164,"errmsg":"invalid ip 47.113.116.141 ipv6 ::ffff:47.113.116.141, not in whitelist rid: 67120c51-61a9d8f0-27e7af1f"}

4:登录小程序后台管理,把服务器域名和业务域名设置上,就是配置上服务器的域名,不然webview打不开我们写的html页面,如果webview页面打不开公众号文章页面,就去看官方文档吧。

 官方文档链接:小程序webview访问公众号文章提示非业务域名 | 微信开放社区


5:其他说明 

那个html页面是个无感登录页面,我们在这个页面获取到的code,去换取微信用户信息的时候,只能拿到openId和关注时间,其他信息(昵称,头像等)都拿不到,如果想要获取到其他信息,html文件里面的window.location.href重定向到微信授权链接,scope参数换成snsapi_userinfo,这样获取到的code就能拿到其他信息,不过这样话,进入这个html页面,会弹出用户信息授权确认的,比如下面这个。


应该差不多了吧,可能会有些遗漏,毕竟步骤是在是太多了,总得就是本地用测试号,上线换正式公众号。 值得注意的是,通过那个html页面拿到的登录code是有时效的,只有几分钟,如果用户在公众号文章页面停留太久,那这个code可能就过期了。具体怎么解决,是重新再跳一次无感登录页面,还是提示用户,你看着办吧。

目前上线后存在一个问题,电脑版小程序在html页面拿到登录code后跳转微信文章页面,有大概率跳不过去,就下面这个代码有可能没有反应,安卓和ios都没有问题,不知道啥情况,有解决的可以留言一下。


 终于码字完了


码字不易,于你有利,勿忘点赞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/55544.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL中查询语句的执行流程

文章目录 前言流程图概述最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;今天我们一起探讨一下执行一条查询的SQL语句在MySQL内部都发生了什么&#xff0c;让你对MySQL内部的架构具备一个宏观上的了解 流程图 概述 对于查询语句的SQL的执行流程&#xff0c;主要可以分为…

【Linux】<互斥量>解决<抢票问题>——【多线程竞争问题】

前言 大家好吖&#xff0c;欢迎来到 YY 滴Linux系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

[Windows]文件搜索利器Everything(附zip)

前言 写代码过程中&#xff0c;老大突然发一条信息 老大&#xff1a;这周周报发一下。 我&#xff1a;好的。 然后我就 显示桌面打开-我的电脑找到E盘&#xff0c;找到周报文件夹寻找到所有周报中今天的周报复制发送 当我用上Everything之后 打开&#xff0c;输入周报copy发…

Oracle T5-2 ILOM配置

ILOM管理口ip地址配置 连接控制器&#xff08;SP&#xff09;串口&#xff08;RJ45)&#xff0c;进行系统设置 (缺省&#xff1a;9600&#xff0c;8-n-1&#xff0c;root/changeme) …………………. ORACLESP-AK02566506 login: root Password: Detecting screen size; pl…

Axure重要元件三——中继器

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 本节课&#xff1a;中继器 课程内容&#xff1a;认识中继器、中继器基本操作、中继器案例 应用场景&#xff1a;高级表单交互 一、认识中继器 我们不从理论视角去展示…

Android Framework AMS(05)startActivity分析-2(ActivityThread启动到Activity拉起)

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读AMS通过startActivity启动Activity的整个流程的整个流程的第二阶段&#xff1a;从ActivityThread启动到Activity拉起。 第一阶段文…

【Vue】Vue(八)Vue3.0 使用ref 和 reactive创建响应式数据

ref 创建&#xff1a;基本类型的响应式数据 **作用&#xff1a;**定义响应式变量。语法&#xff1a;let xxx ref(初始值)。**返回值&#xff1a;**一个RefImpl的实例对象&#xff0c;简称ref对象或ref&#xff0c;ref对象的value属性是响应式的。注意点&#xff1a; JS中操作…

《拿下奇怪的前端报错》:1比特丢失导致的音视频播放时长无限增长-浅析http分片传输核心和一个坑点

问题背景 在一个使用MongoDB GridFS实现文件存储和分片读取的项目中&#xff0c;同事遇到了一个令人困惑的问题&#xff1a;音频文件总是丢失最后几秒&#xff0c;视频文件也出现类似情况。更奇怪的是&#xff0c;播放器显示的总时长为无限大。这个问题困扰了团队成员几天&…

Java项目-基于Springboot的应急救援物资管理系统项目(源码+说明).zip

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

工业自动化为什么依赖光耦隔离器 --- 腾恩科技

光耦合器隔离器在工业自动化中必不可少&#xff0c;可确保信号传输&#xff0c;同时保护敏感电子设备和人员免受高压影响。选择合适的光耦合器隔离器取决于对操作环境和隔离要求的了解。本文将重点介绍在为工业应用选择光耦合器隔离器时需要考虑的关键因素。 光耦合器隔离器在工…

上传图片到github上,生成链接在Typora中使用(解决Typora的md文件在分享时的丢失问题)

上传图片到github上,生成链接在Typora中使用(解决Typora的md文件在分享时的丢失问题) 在GitHub上从操作 创建一个 GitHub 仓库: 登录 GitHub,创建一个新的仓库来存储图片。 生成 GitHub 令牌: 在 GitHub 中,前往“Settings” > “Developer settings” > “Pers…

AUTOSAR_EXP_ARAComAPI的5章笔记(12)

☞返回总目录 5.4.6 方法 骨架侧的服务方法是抽象方法&#xff0c;必须由继承骨架的服务实现子类进行重写。让我们来看一下我们服务示例中的 Adjust 方法&#xff1a; /*** 对于所有输出和非空返回参数* 生成一个包含非空返回值和/或输出参数的封装结构。*/ struct AdjustOu…

UE4 材质学习笔记08(雨滴流淌着色器/雨水涟漪着色器)

一.雨滴流淌着色器 法线贴图在红色通道和绿色通道上&#xff0c;那是法线的X轴和Y轴&#xff0c;在蓝色通道中 我有个用于雨滴流淌的蒙版&#xff0c;在Alpha通道中&#xff0c;有个时间偏移蒙版。这些贴图都是可以在PS上制作做来的&#xff0c;雨滴流淌图可以直接用笔刷画出来…

永恒之蓝漏洞

MS17-010是微软于2017年3月发布的一个安全补丁&#xff0c;旨在修复Windows操作系统中的一个严重漏洞&#xff0c;该漏洞被称为“永恒之蓝”&#xff08;EternalBlue&#xff09;。这个漏洞影响了Windows的Server Message Block&#xff08;SMB&#xff09;协议&#xff0c;允许…

Java集合剖析3】ArrayList

目录 拓展 1. 在面试时如何讲解一个集合的底层&#xff1f; 2. IDEA如何查看底层源码&#xff1f; 一、ArrayList底层数据结构 二、插入方法的具体实现 三、ArrayList底层原理总结 拓展 1. 在面试时如何讲解一个集合的底层&#xff1f; 底层的数据结构。插入方法的具体实现。…

vue综合指南(六)

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vuet篇专栏内容:vue综合指南 目录 101、Vue 框架怎么实现对象和数组的监听&#xff1f; 102、Proxy 与 Object.d…

10 分钟使用豆包 MarsCode 帮我搭建一套后台管理系统

以下是「 豆包MarsCode 体验官」优秀文章&#xff0c;作者把梦想揉碎。 十分钟使用豆包 MarsCode 搭建后台管理项目 在这个快节奏的时代&#xff0c;开发者们总是希望能够快速、高效地完成项目的搭建与开发工作。无论是初创企业还是大型公司&#xff0c;后台管理系统都是必不可…

SpringBoot1~~~

目录 快速入门 依赖管理和自动配置 修改自动仲裁/默认版本号 starter场景启动器 自动配置 修改默认扫描包结构 修改默认配置 读取application.properties文件 按需加载原则 容器功能 Configuration Import ​编辑 Conditional ImportResource 配置绑定Configur…

要在 Git Bash 中使用 `tree` 命令,下载并手动安装 `tree`。

0、git bash 安装 git(安装,常用命令,分支操作,gitee,IDEA集成git,IDEA集成gitee,IDEA集成github,远程仓库操作) 1、下载并手动安装 tree 下载 tree.exe 从 tree for Windows 官方站点 下载 tree 的 Windows 可执行文件。tree for Window&#xff1a;https://gnuwin32.source…

鸿蒙应用开发:全面认识鸿蒙系统

前言 随着智能设备的普及和物联网的发展&#xff0c;对操作系统的需求也越来越多样化。鸿蒙操作系统作为一款面向全场景的分布式操作系统&#xff0c;其适用范围非常广泛&#xff0c;从智能手机到家用电器&#xff0c;再到工业设备&#xff0c;都能找到应用场景。特别是在智能…