前言
使用第三方插件
<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实现公众号扫码登录和关注的开发流程。