微信公众号扫码登录的流程为:1、接入公众号;2、获取accessToken;3、生成带 Ticket 二维码;4、接收事件推送、回复文本消息;5、校验是否扫描完成。
1.接入公众号:具体的操作方法请参考官网:
链接:[https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html](https://link.segmentfault.com/?enc=yFxuCBpi%2FOztIpDbK4OLGg%3D%3D.IQNzJ0bJGjMnpQC1rmC2ztRdfRlgwcF3Pk0Ezzqgz7xGq2tBMUOoXxp3ua4ltxT0QfKgZdqvNougH24U59vRLkbHEXgeJwArpQaAiCbxBJcnfXl83tcy5PrEXB%2BiQvcm)
配置自己的url(接口回调地址):
例如: http://ww.wyz.com/wx/auth/check
另请注意,微信公众号接口必须以http://或https://开头,分别支持80端口和443端口。
微信回调到自己系统的时候ip 是不确定的,请不要限制访问(例如不允许外省访问的限制)
2.获取accessToken:
链接:[https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html](https://link.segmentfault.com/?enc=kjJspzxIQZF0tx3B2jOq9g%3D%3D.0ApPvb1IgNirD4pJ6UpM13mMJ1zWfLWjNDgMwHq9U7YNK8Ggl3zY%2FXiMOGGg6kNAQqhN235Jokahp8jhwNfi5SoFE%2FCRHDPF5UbpWzXuDjRmq0sPeCNh5b44HtK89oKk)
public class getAccessTokenUtils{
// 令牌自定义标识
@Value("${token.header}")
private String header;
// 令牌秘钥
@Value("${token.secret}")
private String secret;
private static String appId = "wx1796034498cf195a";
private static String appSecret = "d5bf2b3d40220deebffc27819ed37334";
// 获取access_token地址
private static String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
@Resource
private RedisCache redisCache;
public String getAccessToken() {
String accessToken = "";
if (redisCache.getCacheObject("wx_access_token") == null) {
String url = tokenUrl.replace("APPID", appId).replace("APPSECRET", appSecret);
HttpRequest get = HttpUtil.createGet(url);
HttpResponse execute = get.execute();
String body = execute.body();
if (body != null && !"".equals(body)) {
try {
JSONObject obj = JSON.parseObject(body);
if (obj.get("access_token") != null) {
accessToken = obj.get("access_token").toString();
redisCache.setCacheObject("wx_access_token", accessToken, Integer.parseInt(obj.get("expires_in").toString()), TimeUnit.SECONDS);
}
} catch (Exception e) {
System.out.println("获取token失败:" + body);
}
}
} else {
accessToken = redisCache.getCacheObject("wx_access_token");
}
return accessToken;
}
}
3.生成带 Ticket 二维码
链接:[https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html](https://link.segmentfault.com/?enc=2mV2OtsEXPVf%2FKtIOQYxUg%3D%3D.7VbgDgPk3mxHArpMm8Dnqsuy0CKNMdbk56%2B3s2KLmf%2FjLrKQvvHRrzLD%2FLYxRXRFqqKYcJ%2FMFJIjOZvLAOFlYSTXcvfxAPSDg62eVUym5ULit%2BjeO8UYb1rZAwWELGjpAgZ0rUL%2F%2BgKo6wx6CxcQRQ%3D%3D)
public String createQrCode() {
String qrcodeImg = "";
String wx_qrcode_img = redisCache.getCacheObject("wx_qrcode_img");
if (StringUtil.isNotEmpty(wx_qrcode_img)) {
qrcodeImg = wx_qrcode_img;
} else {
String accessToken = getAccessTokenUtils.getAccessToken();
if (StringUtil.isNotEmpty(accessToken)) {
String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken;
String param = "{\"action_name\": \"QR_LIMIT_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": 123}}}";
String result = HttpUtils.sendPost(url, param);
JSONObject jsonObject = JSON.parseObject(result);
String ticket = jsonObject.get("ticket").toString();
try {
ticket = URLEncoder.encode(ticket, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
qrcodeImg = String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket);
redisCache.setCacheObject("wx_qrcode_img", qrcodeImg, 365, TimeUnit.DAYS);
}
}
return qrcodeImg;
}
4.接收事件推送、回复文本消息
接受推送事件:
链接:[https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html](https://link.segmentfault.com/?enc=VlDq4%2FG8BAkeGa%2FTea6aoA%3D%3D.Dmc61JZQbJzyaxX8B%2BJZd%2BWGjKStKy%2BX%2BsQM0WFkQ9lhY9Qy7qFGcR0nJsYjvn4EONG1lh8IcbPS0K7MVzmp0DmucCu2qFYkTMLNGd4uAzufuvjopMivGWQ5ZFj6oCqTxFstHM%2BOvdGmmBaZ3bANKVOxiO3R3Dt4NNeqspSxPogq1UyibXmTt6gWm96I78UYTcjKNDsqgZs96W1FmkWmK%2BaYmMCdHVhFs8WpECGYuyXtIpBCAbGi1WtHp4FXrk5M)
设置回复文本消息:[https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html](https://link.segmentfault.com/?enc=yknR0ZA19HdA2lU%2BR%2F56hg%3D%3D.GtfKr%2Bq%2BzhVEwbyweCjgoUFzppp4LvE0KfKsZKd2rBhkybZt5l0hrmzAx0FJpNraAPbWxJqx4f%2FFcCkxF005ICNru29V7jx9L0ejp7LP0DmaQWF%2Fyr4%2BBmm85wxGnv5trFJdJM6VeeMEjl%2Bu766o6w%3D%3D)
当前端获取到二维码的链接以后,进行访问将会得到二维码,用户打开微信扫描二维码后,微信将会回调我们一开始配置好的那个回调地址(http://ww.wyz.com/wx/auth/check)
以下是实现代码:
/**
* 公众号扫码登录回调接口
*/
@Slf4j
@RestController
@RequestMapping("/wx/auth")
public class WeixinServerController {
@Autowired
private WeixinUserService weixinUserService;
@Resource
private SysUserMapper sysUserMapper;
@Value("${weixin.token}")
private String token;
/**
* 接收事件推送
*
* @param request
* @return
*/
@GetMapping(value = "/check")
public String weixinCheck(HttpServletRequest request) {
log.info("微信回调开始(get)");
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce)) {
return "";
}
weixinUserService.checkSignature(signature, timestamp, nonce);
return echostr;
}
/**
* 接收事件推送:在用户扫码后微信服务端会回调改接口
*
* @param requestBody
* @param signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
* @param timestamp 时间戳
* @param nonce 随机字符串
* @return
*/
@PostMapping(value = "/check")
public String weixinMsg(@RequestBody String requestBody, @RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) {
log.info("微信回调开始(post)");
log.info("requestBody:{}", requestBody);
log.info("signature:{}", signature);
log.info("timestamp:{}", timestamp);
log.info("nonce:{}", nonce);
String strs = "签名验证失败";
String[] arr = new String[]{token, timestamp, nonce};
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (String str : arr) {
content.append(str);
}
String tmpStr = DigestUtils.sha1Hex(content.toString());
if (tmpStr.equals(signature)) {
log.info("签名验证成功");
ReceiveMessage receiveMessage = WeixinMsgUtil.msgToReceiveMessage(requestBody);
// 扫码登录
if (WeixinMsgUtil.isScanQrCode(receiveMessage)) {
log.info("扫码登录事件");
return handleScanLogin(receiveMessage);
}
// 关注
if (WeixinMsgUtil.isEventAndSubscribe(receiveMessage)) {
log.info("关注事件");
return receiveMessage.getReplyTextMsg("欢迎关注公众号");
} else {
log.info("取关事件");
//取关
String openId = receiveMessage.getFromUserName();
SysUser user = new SysUser();
user.setOpenid(openId);
List<SysUser> sysUsers = sysUserMapper.selectUserList(user);
if (CollectionUtils.isNotEmpty(sysUsers)) {
for (SysUser sysUser : sysUsers) {
sysUserMapper.updateOpenIdByPhone(sysUser.getPhonenumber());
}
}
}
strs = receiveMessage.getReplyTextMsg("收到(自动回复)");
}
//设置回复文本消息
return strs;
}
/**
*回复文本消息
*
* @param receiveMessage
* @return
*/
private String handleScanLogin(ReceiveMessage receiveMessage) {
String qrCodeTicket = WeixinMsgUtil.getQrCodeTicket(receiveMessage);
if (WeixinQrCodeCacheUtil.get(qrCodeTicket) == null) {
String openId = receiveMessage.getFromUserName();
WeixinQrCodeCacheUtil.put(qrCodeTicket, openId);
log.info("openId:{},ticket:{}设置成功",openId,qrCodeTicket);
}
//判断该用户在系统中是否存在
if (sysUserMapper.selectByOpenid(receiveMessage.getFromUserName()) == null) {
log.info("已关注为绑定手机号");
//todo 返回自己业务需要的文字
return receiveMessage.getReplyTextMsg("欢迎关注公众号!");
} else {
log.info("已登录成功");
//todo 返回自己业务需要的文字
return receiveMessage.getReplyTextMsg("你已成功登录!");
}
}
}
5.校验是否扫描完成
扫码以后,前端得一直轮询该方法,直到能够获取到token
/**
* 校验是否扫描完成 (获取二维码 /qrcode)
* 完成,返回 token
* 未完成,返回 check faild
*
* @param ticket
* @return
*/
@GetMapping(value = "/user/login/qrcode")
public AjaxResult userLogin(String ticket) {
// todo 扫码成功后的逻辑
String openId = WeixinQrCodeCacheUtil.get(ticket);
// todo 生成令牌
String token ="自己的token";
HashMap<Object, Object> temp = new HashMap<>();
temp.put("token", token);
ajax.put("data", temp);
return ajax;
}
log.info("登录失败,ticket:{}", ticket);
return AjaxResult.success("");
}