微信公众号开发接入,利用微信公众平台申请测试号进行本地开发

一、前言

随着微信的普遍,年轻一代逐渐从QQ转到微信的使用。界面简洁,功能强大,男女老少皆宜是微信的特点,也正是靠着这一特点,从而使得微信在国内成为了社交软件的巨头。因此在微信端里就衍生了很多的产品需要进行开发,满足需求。
本文主要说的是服务号的开发,与微信服务器进行交互,通过微信公众号的Oauth2授权,将本地开发的内容在微信中进行展现和交互。因为公众号申请需要时间和经历还需要有企业的相关资质,作为个人开发,可以首先在微信公众平台上申请测试号,通过测试号进行与微信服务器的交互授权,页面调试。在开始之前,小伙伴们需要了解自己的需求,微信中分为 企业号、服务号以及订阅号,不同的号有不同的功能,详情可以自己去微信官网查看自己需要开发什么类型的账号。
在这里插入图片描述

二、开发接入

2.1 微信公众平台接口测试账号申请

前言中提到,在开发中我们一半将从微信官方申请的公众号作为自己的产线环境,那么aut环境可以利用测试账号进行开发,从而也能达到与微信的交互。测试账号的申请链接如下
测试账号申请
进入后,看到如下的画面。进行登陆
在这里插入图片描述
登陆后看见如下画面:
在这里插入图片描述
因为我已经配置过,所以展现的是配置后的相关参数,若第一次进入,则需要配置自己的相关参数。首先要注意的是系统会为你生成一个appId,appsercet,后面我会说这个的作用是什么。现需要配置URL、Token。
说具体点,这个所谓的URL则是自己在代码中需要进行与微信进行Token验证交互的一个方法。当配置完以后,微信会利用配置的url进行一个http的请求发起,注意,这个发起的方式是get请求,因此代码中提供出的接口需要将请求头设置为get请求:
method=(RequestMethod.GET) 在方法中需要对微信发送来的消息进行验证,下面的token需要与自己项目中配置的token一致,微信在发送请求过来后,会带过来签名、时间戳、随机数进行校验,若匹配成功,则可进行下一步,若不一致,则匹配失败。需要注意的是,这个url需要有自己的域名,我个人是在Sunny-Ngrok上进行申请的内网穿透,赠送了一个域名。

2.2 sunny-Ngrok 内网穿透账号申请

点击申请账号
在这里插入图片描述
当创建完账号以后,会有上图所示,其实开通域名的方法有很多种,在此我只是给我一个参考方法。然后点击开通隧道,购买隧道
在这里插入图片描述
10块钱一个月,并不是很贵,当买过后,点击隧道管理
在这里插入图片描述
会有一条购买过后的记录,并且可查看到赠送的域名,同时查看状态,此时的状态为离线状态,如果是离线状态,配置到微信公众平台上微信发起的请求并不能够从本机进行接受,所以,需要在官网上下载客户端进行隧道的启动。
cmd命令行进到sunny.exe所在的目录执行
sunny.exe clientid 隧道id
在这里插入图片描述
如上图所示,将域名指向自己的本机ip和端口号后,就可以进行公众号上的配置提交。在自己本机的代码中,我进行如下的配置:

@RequestMapping(value = "/wx/wxmsgreceive", method = {RequestMethod.GET})
public void verifywxtoken(HttpServletRequest request, HttpServletResponse response) throws Exception {
    request.setCharacterEncoding("UTF-8");
    response.setCharacterEncoding("UTF-8");

    logger.info("开始校验信息是否是从微信服务器发出");
    // 签名
    String signature = request.getParameter("signature");
    // 时间戳
    String timestamp = request.getParameter("timestamp");
    // 随机数
    String nonce = request.getParameter("nonce");
    // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败

    String result = Sha1.gen(SERVER_TOKEN, timestamp, nonce);
    if (signature.equals(result)) {
        // 随机字符串
        String echostr = request.getParameter("echostr");
        logger.debug("接入成功,echostr {}", echostr);
        response.getWriter().write(echostr);
    }
}

微信验证代码

public static String gen(String token, String timestamp, String nonce) throws NoSuchAlgorithmException {
   String[] arr = new String[]{token, timestamp, nonce};
   Arrays.sort(arr);
   StringBuffer content = new StringBuffer();
   for (String a : arr) {
      content.append(a);
   }
   return DigestUtils.sha1Hex(content.toString());
}

因为我配置的请求头是/wx/wxmsgreceive,所以在公众平台的配置页面就是域名+/wx/wxmsgreceive。
此时,当网页上点击提交时,微信就会向本机发送请求认证,确保本机的服务一定是开启状态。
在这里插入图片描述
这是官方给出的说明,因此在我们开发的项目中需要提供一个暴露的接口进行微信服务器的校验,主要是校验在微信公众号网页端填写的token验证。官方有文档说明,如下:
在这里插入图片描述
因此在项目中,放在配置文件或者常量池中的token一定要和在微信公众号接入中填写的token是一个字符串。若填写的不一样或者方法中的解密对比有问题,网页端点击确定时,会出现失败的字样。
在这里插入图片描述

在这里插入图片描述

2.3 微信开发者工具准备

下载微信开发者工具。
点击下载微信开发者工具
在这里插入图片描述
下载完成后,进行扫码登陆
在这里插入图片描述
选择微信公众号网页开发
在这里插入图片描述
在这里插入图片描述
这个工具就是下一步开发时访问url、前端调试的必备工具。

2.3 Oauth2授权

此时已经将测试号配置完毕,那么接下来我们如何开发呢?在微信公众号页面,有的页面需要根据用户身份进行回显不同的信息,比如用户的还款列表等,这些实现首先要获得个人信息而后进行数据库的相关交互,需要拿到用户的唯一标识。在微信中,每个人有且只有一个的唯一标识则是openId,这个openId需要通过微信的授权,回调,去获取。同时,在微信开放文档上也有详细的介绍授权的方法。具体如何去授权可以根据自己的业务需求去使用。在实际业务当中,根据项目的特征来进行不同的固化用户,我们做的就是将用户固化到数据库中,生成一个UUID将UUID放入cookie。授权很关键,有了它的控制我们可以通过获取当前用户的openid进行比较 是会员还是普通会员,是非管理员还是管理员等,这样对页面的跳转起到了至关重要的作用。同时,在代码中,也许有的静态页面不一定需要进行授权,任何人都可以去看。所以在代码中,我就用了过滤器,对前台的.css、.js、.html等一系列的直接放行,对于特殊的请求,把它拉过来然后去请求微信的授权,比如我在项目中对于.go的请求就需要进行验证,通过微信回调获取当前用户信息、固化用户。代码如下:

@WebFilter(urlPatterns = "/*", filterName = "authFilter")
public class AuthFilter implements Filter {

	private final Logger logger = LoggerFactory.getLogger(AuthFilter.class);

	@Autowired
	private GeneralConfigService gcService;
	@Autowired
	private WxUserVerify wxUserVerify;

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {

	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
			throws IOException, ServletException {
		
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		
		try {
			String url = request.getRequestURI();
			logger.info("URL:" + url);

			// 1.请求内容为WEB静态内容时直接放行
			if (StringUtil.separatorEndWith(gcService.getStaticResourceConfig(), url)) {
				chain.doFilter(servletRequest, servletResponse);
				return;
			}
			// 2.不是静态资源进行验证
			if(!checkUrl(request, response, url)){
				return;
			}

			chain.doFilter(servletRequest, servletResponse);
			return;
			
		} catch (FilterException e1) {
			//自定义异常,这里可以直接把错误信息抛出去,不需要记录日志,因为这里是业务异常。
			ResponseUtil.error(response, e1.getMessage());
		} catch (Exception e2) {
			//无法捕捉的异常,不能直接把错误信息抛出去,需要包装下错误信息
			logger.error(e2.getMessage(), e2);
			ResponseUtil.error(response, "系统异常,请稍后再试...");
		}
	}

	/**
	 * 与数据库配置url进行匹配
	 * @param request
	 * @param response
	 * @param url
	 * @return
	 */
	private boolean checkUrl(HttpServletRequest request, HttpServletResponse response, String url) {
		boolean passFlag=true;
		//与数据库进行匹配
		Map<String, String> urlConfig = getUrlConfig();
		//比较匹配项
		String substring = url.substring(url.lastIndexOf("/") + 1);
		if (!urlConfig.containsKey(substring)) {
			return passFlag;
		}
		CallBackMsg msg = wxUserVerify.doVerify(request, response);
		if (msg.getResultCode().equals(CallBackMsg.WX_VALID_CONTINUE)) {
			passFlag=false;
		} else if (msg.getResultCode().equals(Const.ERROR_CODE)) {
			throw new FilterException(FilterExceptionEnum.ERROR_WX_VALID_FAILE);
		}
		return passFlag;
	}

	/**
	 * 获取地址配置信息
	 * @return
	 */
	private Map<String, String> getUrlConfig() {
		// 3.获取需要验证用户的请求配置
		Map<String, String> configAddressList = gcService.getConfigAddressList();
		logger.info(configAddressList.toString());
		if (configAddressList.isEmpty()) {
			logger.error("urlValidConfig为空,请检查!");
			throw new FilterException(FilterExceptionEnum.ERROR_VALIDCONF_NULL);
		}
		return configAddressList;
	}
	@Override
	public void destroy() {

	}

}

通过建立一个类 实现Filter过滤器,进行校验,@WebFilter(urlPatterns = “/*”, filterName = “authFilter”)对所有的请求进行过滤拦截,里面封装了方法 当是特定的一些请求时则会拉取进行一个授权

private CallBackMsg impower(HttpServletRequest request, HttpServletResponse response) {
		CallBackMsg msg = new CallBackMsg();
		String code = request.getParameter("code");
		try {
            // 从配置获取access_token
            String appId = getWxParams("wxAppid");
            String secret = getWxParams("wxSecret");
			String accessTokenUrl = gcService.getUrlWxFwGetToken() + "?appid=" + appId + "&secret=" + secret + "&code="
					+ code + "&grant_type=authorization_code";
			logger.info("accessTokenUrl:" + accessTokenUrl);
			String accessTokenResult = "";
			if (StringUtil.isEmpty(proxyFlag)) {
				proxyFlag = gcService.getProxyFlag();
			}
			if (Const.PROXY_FLAG_USED.equals(proxyFlag)) {
				accessTokenResult = HttpUtils.sendPostHttpsViaProxy(accessTokenUrl, "", gcService.getProxyIp(),
						gcService.getProxyPort());
			} else {
				accessTokenResult = HttpUtils.sendPostHttps(accessTokenUrl, "");
			}
			logger.info("accessTokenResult:" + accessTokenResult);

			JSONObject accessTokenJson = JSONObject.parseObject(accessTokenResult);
			String accessToken = accessTokenJson.getString("access_token");
			logger.info("accessToken:" + accessToken);
			String openid = accessTokenJson.getString("openid");
			logger.info("openid:" + openid);

			// 查询数据库中的用户数据
			String reqUrl = gcService.getSkylarkServiceUrl() + "wechat/searchwxuserinfo";
			ResponseDto responseQuery = this.restInvoke(reqUrl, getQueryParam(openid, null, 1));

			String userInfoDb = responseQuery.getRspMesg();
			JSONObject userInfoDbJson = JSONObject.parseObject(userInfoDb);
			String uuid = "";
			// 数据库不存在该用户则保存数据库,存在则返回已存在的UUID
			if (StringUtils.isEmpty(userInfoDbJson)) {
				// 查询微信中的用户数据
				uuid = UUIDGenerator.genUUID();
				// 获取用户信息
				String userInfoUrl = gcService.getUrlWxFwGetUser() + "?access_token=" + accessToken + "&openid="
						+ openid + "&lang=zh_CN";
				logger.info("userInfoUrl:" + userInfoUrl);
				
				String userInfoResult = "";
				if (Const.PROXY_FLAG_USED.equals(proxyFlag)) {
					userInfoResult = HttpUtils.sendPostHttpsViaProxy(userInfoUrl, "", gcService.getProxyIp(),
							gcService.getProxyPort());
				} else {
					userInfoResult = HttpUtils.sendPostHttps(userInfoUrl, "");
				}
				logger.info("userInfoResult:" + userInfoResult);

				JSONObject userInfoJson = JSONObject.parseObject(userInfoResult);
				// 固化用户
				String addReq = gcService.getSkylarkServiceUrl() + "wechat/addwxuserinfo";
				ResponseDto addResp = this.restInvoke(addReq,
						getEntityParam(uuid, openid, userInfoJson));
				// 失败另处理
				if (!(Const.SUCCESS_CODE).equals(addResp.getRspCode())) {
					// 保存数据失败,认证失败
					msg.setResultCode(Const.ERROR_CODE);
					return msg;
				}
			} else {
				uuid = userInfoDbJson.getString("id");
			}

			// 权限:本项目中的类都可以访问该cookie,存储到客户端
			Cookie cookie = new Cookie(Const.TOKEN, uuid);
			cookie.setPath("/");
			response.addCookie(cookie);
			//将token存入session
			HttpSession session = request.getSession();
			session.setAttribute("authToken",uuid);
		} catch (IOException e) {
			logger.error(e.getMessage());
		}
		msg.setResultCode(Const.SUCCESS_CODE);
		return msg;
	}

因为项目中用了代理服务器,而本地开发并不需要,所以在方法上稍微有点乱,但大体的思路就是,客户端发起的是带有特殊后缀的请求时候,通过过滤器进行拦截下来,进行一个与微信服务器交互授权的过程,在授权成功后,提供给微信一个回调地址并且将微信带回来的用户信息在表中进行存储,同时,将当前用户在库表中生成的唯一ID标识用cookie域进行存储。
在这里插入图片描述
所以可以把获得的openid放入cookie。而后可将此openId进行固化,也就是存在数据库中。在一次的会话中,如果点击到了某一个页面需要用到微信授权时,可以先从cookie域中进获取,如果cookie没有,则向微信发起授权请求,而后将openid拿到后,进行存储cookie和固化。实现起来是有一些步骤手段的,在微信开放平台有相关demo,如果需要我提供,欢迎各位留言,我会将后续授权的代码贴上来供大家参考。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页