首页JavaBee 正文

微信公众号开发之扫码关注并登陆实战

时间: 2022年10月14日 浏览 60

一.思路分析:
1.使用公众号接口生成二维码。
2.系统接收微信推送过来的事件(关注/扫码)。
3.用户点击关注或者扫码二维码后台都会接收到推送通知,然后根据通知实现自己的业务就可以了。

二.开发环境:
1.idea
2.redis
3.springboot2.x

三.基础环境搭建:
1.新建一个springboot项目
2.添加一个控制器,并运行,保证项目正常。

四.业务开发:


 1.让系统和微信系统对接上,能够接收微信推送事件,在控制器上面新增两个方法

/***
* 微信服务器触发get请求用于检测签名
* @return
*/
@GetMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxCheckSignature(HttpServletRequest request){


//todo 严格来说这里需要做签名验证,我这里为了方便就不做了
String echostr = request.getParameter("echostr");

return echostr;

}
/**
* 接收微信推送事件
* @param request
* @return
*/
@PostMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxEvent(HttpServletRequest request){

try {
InputStream inputStream = request.getInputStream();

Map<String, Object> map = XmlUtil.parseXML(inputStream);

String userOpenId = (String) map.get("FromUserName");
String event = (String) map.get("Event");
if("subscribe".equals(event)){

logger.info("用户关注:{}",userOpenId);
}else if("SCAN".equals(event)){

logger.info("用户扫码:{}",userOpenId);
}
logger.info("接收参数:{}",map);

} catch (IOException e) {
e.printStackTrace();
}
return "success";

}

2.在公众号后台设置URL和token,为了方面演示,我使用了公众号测试账号,同时使用了natapp做了外网映射,保存验证成功即可:

3.用手机扫一下自己的公众号测试专用二维码:

点击关注看看控制台是否有接收到通知,接收到即可继续往下:
2.注入RestTemplate用于http请求 :

/**
* 注入restTemplate用于http请求
*/
@Configuration
public class RestTemplateConfig {
@Resource
private RestTemplateBuilder templateBuilder;
@Bean
public RestTemplate restTemplate(){
return templateBuilder.build();
}
}

3.新增一个微信服务接口,用于调用微信公众号接口

public interface WxService {
//获取token
String getAccessToken();
//获取生成二维码参数
Map<String,Object> getQrCode();
}

4.实现接口调用, 不清楚请看微信公众号开发文档

yml中配置公众号参数

spring:
# 模版配置
thymeleaf:
cache: false
#redis 配置
redis:
host: 127.0.0.1
database: 1
password: 123456

#公众号参数配置
wx:
gz:
appid: 你的appid
secret: 你的appsecret

接口服务实现:

@Service
public class WxServiceImpl implements WxService {

Logger logger = LoggerFactory.getLogger(WxServiceImpl.class);

@Value("${wx.gz.appid:''}")
private String appid;
@Value("${wx.gz.secret:''}")
private String secret;
@Resource
private RestTemplate restTemplate;
@Resource
private RedisCacheManager redisCacheManager;

@Override
public String getAccessToken() {


String key = "wx_access_token";

//从redis缓存中获取token
if(redisCacheManager.hasKey(key)){

return (String) redisCacheManager.get(key);
}


String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",appid,secret);

ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);

if(result.getStatusCode()== HttpStatus.OK){

JSONObject jsonObject = JSON.parseObject(result.getBody());

String access_token = jsonObject.getString("access_token");
Long expires_in = jsonObject.getLong("expires_in");
//缓存toekn到redis
redisCacheManager.set(key,access_token,expires_in);
return access_token;
}


return null;
}

@Override
public Map<String, Object> getQrCode() {


//获取临时二维码
String url = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s",getAccessToken());
ResponseEntity<String> result = restTemplate.postForEntity(url, "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"test\"}}}", String.class);

logger.info("二维码:{}",result.getBody());

JSONObject jsonObject = JSON.parseObject(result.getBody());
Map<String,Object> map=new HashMap<>();
map.put("ticket",jsonObject.getString("ticket"));
map.put("url",jsonObject.getString("url"));

return map;
}
}

5.ok,接下来就是在控制器中新增一个首页和一个登陆和登陆成功方法

@GetMapping("")
public String index(){

return "index";
}

@GetMapping("/login")
public String login(){

return "login";
}

@GetMapping("/success")
public String loginSuccess(){

return "登陆成功";
}

简单html页面

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>微信公众号扫码关注登陆实现</title>
</head>
<body>
<a href="/login">扫码登陆</a>

</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>

<div style="width: 200px;margin: 50px auto">
<div id="qrcode"></div>
</div>

<script type='text/javascript' src='http://cdn.staticfile.org/jquery/2.1.1/jquery.min.js'></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script>
$(function () {
//获取二维码参数
$.get('/getQrCode',function (res) {
//生成二维码
$('#qrcode').qrcode(res.url);

//轮训获取用户扫码登陆状态
var task = setInterval(function () {

$.post('/checkLogin',{ticket:res.ticket},function (ok) {

//扫码成功登陆成功
if(ok){

clearInterval(task)
location.href='/success'

}

})
},2000)
})
})

</script>
</body>
</html>

用到的maven依赖

<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>

自己用dom4j简单解析xml

public class XmlUtil {

/**
* 简单解析xml
* @param in
* @return
*/
public static Map<String,Object> parseXML(InputStream in){
Map<String,Object> map=new HashMap<>();
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(in);
Element root = document.getRootElement();
Iterator iterator = root.elementIterator();
while (iterator.hasNext()){

Element element = (Element) iterator.next();
map.put(element.getName(),element.getStringValue());

}
} catch (DocumentException e) {
e.printStackTrace();
}

return map;
}

}

自己简单包装的redis缓存工具

@Component
public class RedisCacheManager {

@Resource
private RedisTemplate<String,Object> redisTemplate;


public void set(String key,Object value,long expire){

redisTemplate.opsForValue().set(key,value,expire, TimeUnit.SECONDS);
}

public Object get(String key){

return redisTemplate.opsForValue().get(key);
}


public Boolean delete(String key){

return redisTemplate.delete(key);
}

public boolean hasKey(String key) {

return redisTemplate.hasKey(key);
}
}

改造controller

@Controller
public class HomeController {
Logger logger = LoggerFactory.getLogger(HomeController.class);


@Resource
private WxService wxService;
@Resource
private RedisCacheManager redisCacheManager;


/**
* 首页
* @return
*/

@GetMapping("/")
public String index(){

return "index";
}

/**
* 登陆页面
* @return
*/
@GetMapping("/login")
public String login(){

return "login";
}

/**
* 用于检测扫码和关注状态
* @return
*/
@PostMapping("/checkLogin")
@ResponseBody
public Object checkLogin(String ticket){
//如果redis中有ticket凭证则说明用户已扫码说明登陆成功
if(redisCacheManager.hasKey(ticket)){
//扫码通过则删除
redisCacheManager.delete(ticket);
return true;
}
return false;
}

/**
* 获取二维码参数
* @return
*/
@GetMapping("/getQrCode")
@ResponseBody
public Object getQrCode(){
return wxService.getQrCode();
}


/**
* 登陆成功跳转
* @return
*/
@GetMapping("/success")
@ResponseBody
public String loginSuccess(){
return "登陆成功";
}

/***
* 微信服务器触发get请求用于检测签名
* @return
*/
@GetMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxCheckSignature(HttpServletRequest request){
//todo 严格来说这里需要做签名验证,我这里为了方便就不做了
String echostr = request.getParameter("echostr");
return echostr;
}
/**
* 接收微信推送事件
* @param request
* @return
*/
@PostMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxEvent(HttpServletRequest request){
try {
InputStream inputStream = request.getInputStream();

Map<String, Object> map = XmlUtil.parseXML(inputStream);

String userOpenId = (String) map.get("FromUserName");
String event = (String) map.get("Event");
if("subscribe".equals(event)){

// TODO:获取openid判断用户是否存在,不存在则获取新增用户,自己的业务

//自己生成的二维码不管是关注还是扫码都能取到ticket凭证,这里我使用Ticket作为每次二维码的唯一标识
String ticket = (String) map.get("Ticket");
redisCacheManager.set(ticket,"",10*60);

logger.info("用户关注:{}",userOpenId);
}else if("SCAN".equals(event)){

//自己生成的二维码不管是关注还是扫码都能取到ticket凭证
String ticket = (String) map.get("Ticket");
redisCacheManager.set(ticket,"",10*60);
logger.info("用户扫码:{}",userOpenId);
}
logger.info("接收参数:{}",map);

} catch (IOException e) {
e.printStackTrace();
}
return "success";
}

}

说明: 通过微信获取二维码参数时里面有ticket和用户扫码之后都会携带这个参数,所以我就将就使用这个凭证作为用户是否扫码的判断了。即,当用户扫我们生成的二维码,收到关注或者扫码事件时,说明用户已扫码或关注,此时将这个二维码的ticket存到redis中, 与前端传过来的ticket对比,如果一致则说明扫码成功,跳转到登陆成功页面!