JAVA实现公众号扫码登录和关注功能实战
前言
使用第三方插件
<dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-mp</artifactId><version>4.6.0</version>
</dependency>
准备APPID和appSecet
- 登录微信公众号后台,复制appid和appsecuret。切记如果开发者秘钥已经在使用,请勿重置,否则导致已上线应用无法使用。
- 微信配置,习惯用yml格式
wechat:appId: wx472934ed71cXXXsecret: 5cf9107ede023fa45ab626c444123a2ftoken: f84542aa3ca2f7e92984dd123683fdacaesKey: M5jSic8xaq5YKb8aMMgUo4oaZAs23kLMJ61BcX8Q123
-
初始化配置
@Slf4j @Configuration public class WxConfiguration {@Autowiredprivate WechatAccountConfig wechatAccountConfig;@Beanpublic WxMpService wxMpService() {WxMpService wxMpService = new WxMpServiceImpl();wxMpService.setWxMpConfigStorage(wxMpConfigStorage());return wxMpService;}@Beanpublic 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 {@Autowiredprivate WxMpService wxMpService;@Autowiredprivate 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 {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate WxMpService wxMpService;@Autowiredprivate INatMemberWechatService natMemberWechatService;@Overridepublic 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 {// 获取 ticketticket = 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 和//生成的uuidTicketVo vo = new TicketVo();vo.setImageBase(qrUrl);vo.setUuid(uuid);return vo;}
@Overridepublic 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 "";}
复制
@Overridepublic 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;}@Overridepublic 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实现公众号扫码登录和关注的开发流程。
相关文章:

JAVA实现公众号扫码登录和关注功能实战
前言 使用第三方插件 <dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-mp</artifactId><version>4.6.0</version> </dependency>准备APPID和appSecet 登录微信公众号后台,复制ap…...

初识Mysql/备份,基础指令
1,MySQL登录指令: mysql -h 127.0.0.1 -P3306 -u -p 其中,-h指明登录部署了mysql服务的主机 -P指明要访问的端口号, -u指明登录用户 -p输入密码 2,数据库基础 mysql:表示的是客户端 mysqld&…...

没想到吧!线稿上色居然可以这么简单
前言 在创意无限的数字艺术世界里,艺术创作中的线稿上色,向来是件既费时又需技巧的活儿,寻找一款既能激发灵感又能简化繁琐流程的工具,是每位艺术家心中的向往。 今天,为大家推荐一款革命性的线稿上色AI工具——千鹿…...

修改Docker的默认存储路径
docker默认存储路径:/var/lib/docker/ 执行 docker info 查看,得到以下信息 Docker Root Dir: /var/lib/docker/Debug Mode: falseRegistry: https://index.docker.io/v1/Labels:Experimental: falseInsecure Registries: 1.修改docker配置 要修改默认…...

深入计算机语言之C++:C到C++的过度
🔑🔑博客主页:阿客不是客 🍓🍓系列专栏:从C语言到C语言的渐深学习 欢迎来到泊舟小课堂 😘博客制作不易欢迎各位👍点赞⭐收藏➕关注 一、什么是C C(c plus plusÿ…...

HR面试篇
一.面试中被问职业规划 HR感兴趣的不是你的职业规划,感兴趣的是你的职业规划和他们公司有没有关系。 或者说他们公司能不能去帮助你去实现你的职业规划。 切忌不要讲不合实际的,比如要在公司赚多少钱等等。 要根据公司的特点,找到切入点,只要讲得积极向上就可以。 二.…...

深度探索Kali Linux的精髓与实践应用
Kali Linux简介 Kali Linux作为全球网络安全领域的首选操作系统之一,其强大的功能性及广泛的适用范围令人瞩目。除了上述基础介绍外,让我们深入探究Kali Linux的几个关键特性及其在实际操作中的具体应用案例。 Kali工具集成:全面的安全工具…...

【在Linux世界中追寻伟大的One Piece】DNS与ICMP
目录 1 -> DNS(Domain Name System) 1.1 -> DNS背景 2 -> 域名简介 2.1 -> 域名解析过程 3 -> 使用dig工具分析DNS 4 -> ICMP协议 4.1 -> ICMP功能 4.2 -> ICMP报文格式 4.3 -> Ping命令 4.4 -> traceroute命令 1 -> DNS(Domain Na…...

信息安全工程师(41)VPN概述
前言 VPN,即Virtual Private Network(虚拟专用网络)的缩写,是一种通过公共网络(如互联网)创建私密连接的技术。 一、定义与工作原理 定义:VPN是依靠ISP(Internet Service Provider&…...

算法:双指针系列(一)
双指针系列 一、移动零(一)题目分析(二)代码展示二、复写零(一)题目分析(二)代码展示三、快乐数(一)题目分析(二)代码展示(…...

跟《经济学人》学英文:2024年09月28日这期 The curse of the Michelin star
The curse of the Michelin star Restaurants awarded the honour are more likely to close, research finds 原文: The twelve new restaurants added to the New York Michelin Guide this month, serving up cuisine ranging from “haute French” to “eco…...

Java Set 的介绍与实现原理
什么是 Set 在 Java 中,Set 是一种集合类型,它不允许重复的元素。Set 接口是 Java Collections Framework 的一部分,主要用于存储不重复的值。常见的实现类包括 HashSet、LinkedHashSet 和 TreeSet。 实现原理 1. HashSet HashSet 是最常…...

我谈均值平滑模板——给均值平滑模板上升理论高度
均值平滑(Mean Smoothing),也称为盒状滤波(Box Filter),通过计算一个像素及其周围像素的平均值来替换该像素的原始值,从而达到平滑图像的效果。 均值平滑通常使用一个模板(或称为卷…...

WordPress添加https协议致使后台打不开解决方法
由于删除WordPress缓存插件后操作不当,在加上升级处理,致使茹莱神兽博客的首页出现了https不兼容问题,WordPress后台也无法登陆,链接被误认为是定向重置次数过多,在网上找了好久的答案。 还有就是求助了好些人…...

如何使用pymysql和psycopg2执行SQL语句
在Python中,pymysql和psycopg2是两个非常流行的库,用于与MySQL和PostgreSQL数据库进行交互。本文将详细介绍如何使用这两个库来执行SQL查询、插入、更新和删除操作。 1. 准备工作 首先,确保已经安装了pymysql和psycopg2库。如果尚未安装&a…...

linux无法使用ll命令
ll命令是ls -l的别名,无法使用通常是该用户没有该别名配置,只需要简单添加即可使用 修改~/.bashrc # 备份 cp ~/.bashrc ~/.bashrc.source # 编辑 vim ~/.bashrc添加如下内容 # 别名 alias llls -l加载配置 source ~/.bashrc...

STM32输入捕获模式详解(上篇):原理、测频法与测周法
1. 前言 在嵌入式系统的开发过程中,常常需要对外部信号进行精确的时间测量,如测量脉冲信号的周期、频率以及占空比等。STM32系列微控制器提供了丰富的定时器资源,其中的输入捕获(Input Capture, IC)模式能实现对信号的…...

面试中遇到的关于Transformer模型的问题有哪些?
Transformer是深度学习中极具影响力的模型架构之一,广泛应用于自然语言处理、计算机视觉等领域。它通过自注意力机制和并行计算等特点,取得了比传统模型(如RNN、LSTM)更优异的性能。本文将针对Transformer的多个关键问题进行详细探…...

【UE】自动添加Megascans所有资产到自己的账户
1. 复制如下代码: ((async (startPage 0, autoClearConsole true) > {const getCookie (name) > {const value ; ${document.cookie};const parts value.split(; ${name});if (parts.length 2) return parts.pop().split(;).shift();}const callCacheA…...

【函数】4.函数的单调性
本节课没有笔记示例,自己做好笔记! 复合函数的单调性 最值 没讲 提醒我...
网格剖分-耳切法效果展示
1.前言 将简单多边形转换成一组由同样顶点组成的三角形集合是计算机图形学中的一个经典问题。问题中,简单多边形是指由一组有序顶点组成的,点V0~点Vn-1。相邻的顶点之间通过边(Vi,Vi-1)连接,并且边(Vn-1,V0)连接起始点…...

电磁力、强相互作用力、弱相互作用力、强核力,以及它们之间的关系
电磁力、强相互作用力、弱相互作用力、强核力,以及它们之间的关系: 电磁力 (Electromagnetic Force): 定义:电磁力是带电粒子之间通过电荷相互作用产生的力。它由电场和磁场共同作用,影响带电粒子的运动。传递粒子:电磁…...

2.安装keepalived详细过程
1.下载地址: keepalived-2.3.1 keepalived-2.1.2 keepalived-2.0.18 三个版本tar包 2.keepalived安装详细过程 (1) 解压keepalived tar包 tar -zxvf keepalived-2.0.18.tar.gz(2)进入keepalived目录然后配置 #进入keepalived目录 cd keepalived-2.0.18/ #配置keepalived[配…...

面试题1-fail-safe机制与fail-fast 机制
1.定义 Fail-safe 和 Fail-fast,是多线程并发操作集合时的一种失败处理机制。 1.1.Fail-Safe机制 1.1.1.定义 Fail-Safe 机制的设计目标是在发生故障时,系统仍然能够继续运行,尽量避免导致整个系统崩溃。即使发生错误或异常,系统…...

C/C++复习(一)
1.sizeof 关于sizeof我们是经常使用的,所以使用方法就不需要提及了,这里我们需要注意的是,sizeof 后面如果是表达式可以不用括号,并且sizeof实际上不参与运算,返回的是内容的类型大小(size_t类型࿰…...

iOS Object-C 将数组倒置(倒叙)
使用NSArray自带的对象方法:reverseObjectEnumerator 代码如下: NSArray * tempArray [[NSArray alloc]initWithObjects:"a","b","c","d", nil]; //将tempArray转换成["d","c","b","a"]; …...

动态轻量级线程池项目
动态线程池: 使用线程池ThreadPoolExecutor过程中你是否有以下痛点呢? ① 代码中创建了一个ThreadPoolExecutor,但是不知道参数设置多少比较合适。 ② 凭经验设置参数值,上线后发现需要调整,改代码重新发布服务&…...

【AI知识点】批归一化(Batch Normalization)
更多AI知识点总结见我的专栏:【AI知识点】 AI论文精读、项目和一些个人思考见我另一专栏:【AI修炼之路】 有什么问题、批评和建议都非常欢迎交流,三人行必有我师焉😁 批归一化(Batch Normalization,BN&…...

【低代码】前端低代码开发日记2:遇到的问题(1)双向绑定
在前期的快速迭代阶段,虽然界面有些杂乱,但整体功能尚能凑合运行。真正让人头疼的,还是接下来几个关键功能的实现。 遇到的问题 双向绑定 在Vue中,v-model提供了方便的双向绑定功能,它是modelValue属性和onUpdate:m…...

10.9作业
1、鼠标和键盘事件 #include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QMouseEvent>widget::widget(QWidget *parent): QWidget(parent), ui(new Ui::widget) {ui->setupUi(this);this->setWindowFlag(Qt::Fram…...