前言
使用第三方插件
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.6.0</version>
</dependency>
准备APPID和appSecet
- 登录微信公众号后台,复制appid和appsecuret。切记如果开发者秘钥已经在使用,请勿重置,否则导致已上线应用无法使用。
- 微信配置,习惯用yml格式
wechat: appId: wx472934ed71cXXX secret: 5cf9107ede023fa45ab626c444123a2f token: f84542aa3ca2f7e92984dd123683fdac aesKey: M5jSic8xaq5YKb8aMMgUo4oaZAs23kLMJ61BcX8Q123
-
初始化配置
@Slf4j @Configuration public class WxConfiguration { @Autowired private WechatAccountConfig wechatAccountConfig; @Bean public WxMpService wxMpService() { WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxMpConfigStorage()); return wxMpService; } @Bean public WxMpConfigStorage wxMpConfigStorage() { WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl(); log.info("微信配置文件 : {}", JSON.toJSONString(wechatAccountConfig)); wxMpDefaultConfig.setAppId(wechatAccountConfig.getAppId()); wxMpDefaultConfig.setSecret(wechatAccountConfig.getSecret()); wxMpDefaultConfig.setToken(wechatAccountConfig.getToken()); wxMpDefaultConfig.setAesKey(wechatAccountConfig.getAesKey()); return wxMpDefaultConfig; }
@Data @Component @ConfigurationProperties(value = "wechat") public class WechatAccountConfig { private String appId; private String secret; private String token; private String aesKey; }
生成二维码
-
获取二维码方法和扫描状态检查接口
@Slf4j @Api(value = "网关公众号接口", tags = "网关公众号接口") @RestController @RequestMapping("/gateway/wechat") public class WechatSubController { @Autowired private WxMpService wxMpService; @Autowired private IWeiXinService weiXinService; /** * 获取微信生成二维码 */ @ApiOperation(value = "获取微信二维码") @GetMapping(value = "/qrcode/{codeType}") public AjaxResult getQrCode(@PathVariable("codeType") String codeType) { return AjaxResult.success(weiXinService.getQrCode(MsgEventTypeEnum.getTypeEnum(codeType))); } /** * 获取微信生成二维码 */ @ApiOperation(value = "获取扫码结果") @GetMapping(value = "/getScanResult/{uuid}") public AjaxResult getScanResult(@PathVariable("uuid") String uuid) { return AjaxResult.success(weiXinService.getScanResult(uuid)); }
-
接口层
public interface IWeiXinService { TicketVo getQrCode(MsgEventTypeEnum msgEventTypeEnum); String scanQRCodesCallBack(WxMpXmlMessage px); NatMemberWechat getScanResult(String uuid); }
-
实现层
@Slf4j @Service public class WeiXinServiceImpl implements IWeiXinService { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private WxMpService wxMpService; @Autowired private INatMemberWechatService natMemberWechatService; @Override public TicketVo getQrCode(MsgEventTypeEnum msgEventTypeEnum) { // 这里生成uuid 等下扫码验证微信时,就知道是那个用户扫的码 // 这个uuid就是上面声明的全局私有变量 String uuid = UUID.randomUUID().toString(); Map<String, Object> map = new HashMap<>(); map.put("uuid", uuid); map.put("eventType", msgEventTypeEnum.getEventType()); stringRedisTemplate.opsForValue().set(RedisKey.WECHAT_SCAN_UUID_FLAG + uuid, JSON.toJSONString(map), 120L, TimeUnit.SECONDS); WxMpQrCodeTicket ticket = null; try { // 获取 ticket ticket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(JSON.toJSONString(map), 6400); } catch (WxErrorException e) { throw new RuntimeException(e); } String qrUrl = null; try { // 根据 ticket 换取二维码链接 qrUrl = wxMpService.getQrcodeService().qrCodePictureUrl(ticket.getTicket()); } catch (WxErrorException e) { throw new RuntimeException(e); } // 返回二维码链接,我们系统是返回base64编码的,但是代码太长了,我这里直接返回图片的url 和 //生成的uuid TicketVo vo = new TicketVo(); vo.setImageBase(qrUrl); vo.setUuid(uuid); return vo; }
@Override public NatMemberWechat getScanResult(String uuid) { String redisData = Objects.toString(stringRedisTemplate.opsForValue().get(RedisKey.WECHAT_SCAN_UUID_RESULT_FLAG + uuid), null); if (StringUtils.isEmpty(redisData)) { log.debug("redisData is null"); return null; } JSONObject jsonObject = JSONObject.parseObject(redisData); NatMemberWechat natMemberWechat = natMemberWechatService.selectNatMemberWechatByOpenId(jsonObject.getString("openid")); if(natMemberWechat==null){ natMemberWechat=new NatMemberWechat(); natMemberWechat.setOpenId(jsonObject.getString("openid")); natMemberWechat.setUserId(null); } return natMemberWechat; }
到此,扫码和扫描检查的代码已经写完,接下来配置回调。
由于回调是微信服务器外网回调服务器,在本地开发是内网,怎么才能让微信调用本地电脑呢,我们就要借助内网穿透。
内网穿透
- NatCross内网穿透,推荐的原因是免费。可以将局域网个人电脑、服务器映射到公网的内网穿透工具。
- NatCross官网(http://www.natcross.com/)
- 账号注册
- 登录首页如图
- 配置映射,需要实名认证
- 配置映射,获取生成的访问域名地址
- 下载并启动客户端
下载方式2:windows和linux 版本下载,请点击连接 https://pan.baidu.com/s/16k2jFiWuvtNN5Y8CGNUkzw (提取码:hs9d) 运行步骤: windows版本步骤: 1、下载window客户端 2、注册natcross账号 3、修改config.properties配置文件,修改client.key值改为自己注册的客户端秘钥 4、双击执行启动client-start.bat 5、配置添加内网映射或场景映射后,自动连接。 提示:自带jre,无效安装运行环境。 linux版本步骤: 1、下载linux客户端 2、注册natcross账号 3、vi config.properties配置文件,修改client.key值改为自己注册的客户端秘钥 4、执行chmod 777 client-start.sh 授权 5、执行:启动 ./client-start.sh start ,停止 ./client-start.sh stop ,重启 ./client-start.sh restart 6、查看logs日志 tail -100f natcross-client-3.0.0.jar.log 7、配置添加内网映射或场景映射后,自动连接。 提示: 自带jre,无需再次安装运行环境。 如有疑问,可以加QQ客服:2496727282 (早9点-晚10点)
- 启动成功截图:
公众号接口配置,将上一步获取到的地址+请求方式配置到微信后台,如图
扫码登录
公众号回调
-
回调代码块实现
/** * 验证微信服务器 此接口不调用 */ @ApiOperation(value = "验证微信服务器 此接口不调用") @GetMapping(value = "/callback") public String checkSign(HttpServletRequest request) { String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); log.info("\n接收到来自微信服务器的认证消息:[signature:{}, timestamp:{}, nonce:{}, echostr:{}]", signature, timestamp, nonce, echostr); if (!wxMpService.checkSignature(timestamp, nonce, signature)) { log.error("【无效的请求】"); throw new ServiceException("无效的请求", -1); } return echostr; } /** * 响应微信,这一步是关注微信后,响应微信获取信息 */ @ApiOperation(value = "微信扫码响应微信服务器") @PostMapping("/callback") public String scanQRCodesCallBack(HttpServletRequest request, @RequestBody String requestBody) { String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); String openid = request.getParameter("openid"); String encType = request.getParameter("encType"); String msgSignature = request.getParameter("msgSignature"); log.info("\n接收到来自微信服务器的认证消息:[signature:{}, timestamp:{}, nonce:{}, echostr:{},encType:{},msgSignature:{}]", signature, timestamp, nonce, echostr, encType, msgSignature); if (!wxMpService.checkSignature(timestamp, nonce, signature)) { log.error("【无效的请求】"); throw new ServiceException("无效的请求", -1); } if (encType == null) { // 明文传输的消息 WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); log.debug("\n消息内容为:\n{} ", inMessage.toString()); return weiXinService.scanQRCodesCallBack(inMessage); } else if ("aes".equalsIgnoreCase(encType)) { // aes加密的消息 WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxMpService.getWxMpConfigStorage(), timestamp, nonce, msgSignature); log.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); return weiXinService.scanQRCodesCallBack(inMessage); } return ""; }
复制
@Override public String scanQRCodesCallBack(WxMpXmlMessage message) { log.info("总的message:" + JSON.toJSONString(message)); // content 公众号回复用户的文本内容 String content = "欢迎关注公众号!NatCross是内网穿透工具,也是免费的端口映射软件。解决80被封/动态IP/无公网ip问题;适用于发布网站、访问局域网服务器和应用服务。"; String messageType = message.getMsgType(); //消息类型 String messageEvent = message.getEvent(); //消息事件 String fromUser = message.getFromUser(); //发送者帐号 String toUser = message.getToUser(); //开发者微信号 String text = message.getContent(); //文本消息 文本内容 String eventKey = message.getEventKey(); //二维码参数 JSONObject businessParams = JSON.parseObject(eventKey); //从二维码参数中获取uuid通过该uuid可通过websocket前端传数据 log.info("消息类型:{},消息事件:{},发送者账号:{},接收者微信:{},文本消息:{},二维码参数:{}", messageType, messageEvent, fromUser, toUser, text, eventKey); WxMpUser wxMpUser = null; try { wxMpUser = wxMpService.getUserService().userInfo(fromUser); } catch (WxErrorException e) { e.printStackTrace(); } log.info("通过用户openid获取用户信息:" + JSON.toJSONString(wxMpUser)); if (StringUtils.equalsAnyIgnoreCase(WechatEventEnum.unsubscribe.getType(), messageEvent)) { //取消订阅 log.info("取消订阅"); return this.msgStr(message, "取消关注成功!"); } String uuid = businessParams.containsKey("uuid") ? businessParams.getString("uuid") : null; if (StringUtils.isEmpty(uuid)) { //未知关注公众号,默认提示 log.info("未知关注公众号,默认提示"); return this.msgStr(message, content); } String redisData = Objects.toString(stringRedisTemplate.opsForValue().get(RedisKey.WECHAT_SCAN_UUID_FLAG + uuid), null); if (StringUtils.isNotEmpty(redisData) && wxMpUser != null) { if (StringUtils.equalsAnyIgnoreCase(WechatEventEnum.SCAN.getType(), messageEvent)) { String eventType = businessParams.getString("eventType"); if (StringUtils.equalsAnyIgnoreCase(MsgEventTypeEnum.ADD_WARNING_RECEIVER.getEventType(), eventType)) { content = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS) + "添加告警接收人成功!"; } else if (StringUtils.equalsAnyIgnoreCase(MsgEventTypeEnum.USER_LOGIN.getEventType(), eventType)) { content = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS) + "扫描登录成功!"; } JSONObject dataMap = JSON.parseObject(redisData); dataMap.put("openid", wxMpUser.getOpenId()); dataMap.put("unionid", wxMpUser.getUnionId()); dataMap.put("nickName", wxMpUser.getNickname()); dataMap.put("headImgUrl", wxMpUser.getHeadImgUrl()); stringRedisTemplate.opsForValue().set(RedisKey.WECHAT_SCAN_UUID_RESULT_FLAG + uuid, dataMap.toString()); } } // 根据来时的信息格式,重组返回。(注意中间不能有空格) final String msgStr = this.msgStr(message, content); return msgStr; } @Override public NatMemberWechat getScanResult(String uuid) { String redisData = Objects.toString(stringRedisTemplate.opsForValue().get(RedisKey.WECHAT_SCAN_UUID_RESULT_FLAG + uuid), null); if (StringUtils.isEmpty(redisData)) { log.debug("redisData is null"); return null; } JSONObject jsonObject = JSONObject.parseObject(redisData); NatMemberWechat natMemberWechat = natMemberWechatService.selectNatMemberWechatByOpenId(jsonObject.getString("openid")); if(natMemberWechat==null){ natMemberWechat=new NatMemberWechat(); natMemberWechat.setOpenId(jsonObject.getString("openid")); natMemberWechat.setUserId(null); } return natMemberWechat; } private String msgStr(WxMpXmlMessage message, String content) { // 根据来时的信息格式,重组返回。(注意中间不能有空格) final String msgStr = "<xml>" + "<ToUserName><![CDATA[" + message.getFromUser() + "]]></ToUserName>" + "<FromUserName><![CDATA[" + message.getToUser() + "]]></FromUserName>" + "<CreateTime>" + new Date().getTime() + "</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[" + content + "]]></Content>" + "</xml>"; return msgStr; }
- 回调日志测试通过后,如果是vue前后端分离,可以把接口给前端调用。
以上是JAVA实现公众号扫码登录和关注的开发流程。