前言
首先,为什么要生成参数二维码,他有什么用?他的使用场景是?
使用场景: 社长之前做过这样一个项目,他的主要业务,就是排课,上课,以后课文反馈。例如,课前10分钟,我们是不是需要收到短信或者公众号收到一条模板消息(上一篇文章),发消息,给谁发,是不是很关键,在当时,我们项目组的解决方法,就是在公众号里面新增一个菜单入口,通过绑定学生手机账号的方式来实现的。个人感觉有点麻烦。不太方便,个人观点,首先你可以把用户想得很懒,才能打磨出好用的产品。
- 例如,哪些网站为什么要做微信公众号或者手机号验证码登录,如果太复杂,我是会直接放弃使用该产品的,除非没有第二选择。
本文主要是生成微信参数二维码,实现学生账号和家长微信openI快速绑定, 以及能收到系统发送的消息(课后反馈)。
生成参数二维码
pom.xml
<!-- httpclient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.1.0</version>
</dependency>
yml
#公众号配置
wechat:
appid: wx7e2be32199af1111
appkey: 2c01014c25ed48d5a71111
- 输入自己的公众号信息
业务处理方法
package com.zyee.iopace.web.service;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zyee.iopace.web.config.WechatConfig;
import com.zyee.iopace.web.dto.vo.TemplateMsgEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ConfigurationService {
private String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&";
@Autowired
private WechatConfig wechatConfig;
public String getAccessToken() {
String requestUrl = accessTokenUrl + "appid=" + wechatConfig.getAppid() + "&secret=" + wechatConfig.getAppkey();
String resp = HttpUtil.get(requestUrl);
JSONObject result = JSONUtil.parseObj(resp);
System.out.println("获取access_token:" + resp);
String token=result.getStr("access_token");
return token;
}
/**
* 获取用户列表
* @param accessToken
* @return
*/
public JSONObject getUserList(String accessToken) {
String requestUrl = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + accessToken + "&next_openid=";
String resp = HttpUtil.get(requestUrl);
JSONObject result = JSONUtil.parseObj(resp);
System.out.println("用户列表:" + resp);
return result;
}
public JSONObject sendTemplateMsg(TemplateMsgEntity messageVo, String token, String openId,String templateId) {
String requestUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + token;
Map<String,Object> content=new HashMap<>();
JSONObject data = JSONUtil.createObj();
//value 为需要设置的值 color为字体颜色
data.put("title",new JSONObject().put("value",messageVo.getTitle()));
data.put("key1",new JSONObject().put("value",messageVo.getKey1()).put("color","#173177"));
data.put("key2",new JSONObject().put("value",messageVo.getKey2()).put("color","#173177"));
data.put("key3",new JSONObject().put("value",messageVo.getKey3()).put("color","#173177"));
data.put("key4",new JSONObject().put("value",messageVo.getKey4()).put("color","#173177"));
data.put("remark",new JSONObject().put("value",messageVo.getRemark()));
content.put("touser",openId);
content.put("url",messageVo.getUrl());
content.put("template_id",templateId);
content.put("data",data);
String resp = HttpUtil.post(requestUrl,new JSONObject(content).toString());
System.out.println(content.toString());
JSONObject result = JSONUtil.parseObj(resp);
System.out.println("发送消息:" + resp);
return result;
}
/**
* 生成临时二维码
* @param accessToken
* @param sceneId
* @return
*/
public String getTemporaryQR(String accessToken,String sceneId){
//获取数据的地址(微信提供)
String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="+accessToken+"";
//发送给微信服务器的数据 expire_seconds为时间,单位秒,最大2592000,30天,这里设置120秒,QR_STR_SCENE标识可以设置字符串
String jsonStr = "{\"expire_seconds\": 120,\"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": "+sceneId+"}}}";
//将得到的字符串转化成json对象
String ticketJson = HttpUtil.post(url,jsonStr);
JSONObject result = JSONUtil.parseObj(ticketJson);
String ticket=result.getStr("ticket");
return ticket;
}
}
controller类
package com.zyee.iopace.web.controller;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.system.UserInfo;
import com.zyee.iopace.web.dto.vo.TemplateMsgEntity;
import com.zyee.iopace.web.enums.WxTemplateType;
import com.zyee.iopace.web.response.ResponseResult;
import com.zyee.iopace.web.service.ConfigurationService;
import com.zyee.iopace.web.utils.WxUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Locale;
@Slf4j
@Api(tags = "微信测试接口")
@RestController
@CrossOrigin
@RequestMapping("/wx")
public class WxController {
@Autowired
private ConfigurationService configurationService;
/**
* 创建二维码
* @param account
* @return
* @throws Exception
*/
@RequestMapping("/getImg")
@ApiOperation("创建生成二维码")
public void getImg(String account, HttpServletResponse resp) throws Exception {
String accessToken = configurationService.getAccessToken();
//真实业务代码
//1.根据account账号信息查询redis是否存在该ticket数据,ticket有效期最大不超过2592000(即30天)
//2、不存在调用getTemporaryQR返回ticket,存在则直接使用redis里面的请求
//获取ticket
String ticket = configurationService.getTemporaryQR(accessToken,account);
String url2 = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket + "";
try {
URL url = new URL(url2);
HttpURLConnection httpUrl = (HttpURLConnection) url.openConnection();
httpUrl.connect();
httpUrl.getInputStream();
InputStream is = httpUrl.getInputStream();
BufferedImage image = ImageIO.read(is);
// 可通过输出流输出到页面
ImageIO.write(image, "png", resp.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 直接访问http://localhost:9090/wx/getImg?account=‘#123’,查看结果,注意’#123’是学生的账号id,最好是开头增加一个#号,方便后面,取消关注和关注事件里面获取key的值,方便系统知道,是扫的那个学生的码。
到这里我们就可以实现扫码后,跳转到公众号,万里长征还差一步,扫码以后,我是不是需要把学生账号的信息,跟家长的openId进行绑定,这样才可以实现,家长扫码后,自动跟学生账号绑定,还有一种情况,就是家长取消关注后,需要把学生跟家长微信openId的绑定关系进行取消
内外网映射
为什么需要内外网映射? 那是因为公众号回调填写的网站是需要是域名,以及备案的。
使用工具有路由侠、花生壳(买过38一年的)等。
- 花生壳之前可以在公众号里面使用,最近我去使用购买的域名,发现配置验证地址后,前台没有任何的影响,这种情况大概率就是域名被封咯
- 网上免费白嫖的natapp和ngork(都试验过,通过网站可以访问,但是配置到公众号哪里,没有任何的影响应)
花生壳技术客户沟通记录
- 1、下载安装路由侠
- 2、注册登录
- 选择集成端口映射,ip别写127.0.0.1,写实际ip,不然会有 坑
验证
####内网访问的地址
http://localhost:9090/wx/checkSign
####外网访问的地址
http://XXXX.e1.luyouxia.net:24722/wx/checkSign
- 实现localhost:9090和XXXX.e1.luyouxia.net:24722的映射
- 发现后台有日志打印,说明映射成功
扫码后事件
- 复制外网的访问地址,记住后面有用
微信回调方法对应代码
controller类
package com.zyee.iopace.web.controller;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.system.UserInfo;
import com.zyee.iopace.web.dto.vo.TemplateMsgEntity;
import com.zyee.iopace.web.enums.WxTemplateType;
import com.zyee.iopace.web.response.ResponseResult;
import com.zyee.iopace.web.service.ConfigurationService;
import com.zyee.iopace.web.utils.WxUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Locale;
@Slf4j
@Api(tags = "微信测试接口")
@RestController
@CrossOrigin
@RequestMapping("/wx")
public class WxController {
@Autowired
private ConfigurationService configurationService;
/**
* 微信公众号回调
* @param request
* @return
* @throws Exception
*/
@RequestMapping ("/checkSign")
public String checkSign (HttpServletRequest request) throws Exception {
//获取微信请求参数
log.info("接收微信公众号事件触发回调请求");
String signature = request.getParameter ("signature");
String timestamp = request.getParameter ("timestamp");
String nonce = request.getParameter ("nonce");
String echostr = request.getParameter ("echostr");
//参数排序。 token 就要换成自己实际写的 token
String [] params = new String [] {timestamp,nonce,"123456"} ;
Arrays.sort (params) ;
//拼接
String paramstr = params[0] + params[1] + params[2] ;
//加密
//获取 shal 算法封装类
MessageDigest Sha1Dtgest = MessageDigest.getInstance("SHA-1") ;
//进行加密
byte [] digestResult = Sha1Dtgest.digest(paramstr.getBytes ("UTF-8"));
//拿到加密结果
String mysignature = WxUtils.byteToStr(digestResult);
mysignature=mysignature.toLowerCase(Locale.ROOT);
log.info("微信加密,signature:"+signature);
log.info("本地加密,mysignature:"+mysignature);
//是否正确
boolean signsuccess = mysignature.equals(signature);
//逻辑处理
if (signsuccess && echostr!=null) {
//peizhi token
return echostr ;//不正确就直接返回失败提示.
}else{
String userInfo= configurationService.callback(request);
return userInfo;
}
}
}
业务事件类
package com.zyee.iopace.web.service;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zyee.iopace.web.config.WechatConfig;
import com.zyee.iopace.web.dto.vo.TemplateMsgEntity;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Component
public class ConfigurationService {
private String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&";
@Autowired
private WechatConfig wechatConfig;
/**
* 微信回调事件
* @param request
* @return
* @throws Exception
*/
public String callback(HttpServletRequest request) throws Exception{
WxMpXmlMessage message = WxMpXmlMessage.fromXml(request.getInputStream());
if(message.getEvent().equals("SCAN")){
//扫码 eventKey为扫码绑定的值,建议定义一个前缀,方便区分, qrscene_ 为未关注用户的前缀
//为未关注用户的前缀 qrscene_bind_123 关注是不会有qrscene_前缀信息的 bind可以作为我们扫码事件的标识,区分入口,123就是对应的id
// todo 需要提供判断事件,确定是否为扫码绑定的
}else if(message.getEvent().equals("subscribe")){
//关注代码
// todo 需要提供判断事件,确定是否为扫码绑定的
} if(message.getEvent().equals("unsubscribe")){
//取消关注代码
//删除openId为fromUser的数据,openId的值,从fromUser中取
}
System.out.println();
return null;
}
}
- 这里都是伪代码 ,知道怎么实现就行。
验证微信回调
打开测试微信公众号的地址
- 通过如图生成二维码,扫码后,在checkSign方法上断点查看,是否进来。如果能进来,说明微信回调是没有问题的。