微信扫码关注公众号
一、扫码开发步骤
2、根据用户id生成ticket
3、根据生成的ticket再生成微信二维码
4、点击关注将用户的openID关联到用户信息表中
5、取消关联,根据用户id删除对应的openID
二、准备工作:
1、登录微信工作平台的测试号
2、填写接口配置信息
三、API文档查看
1、登录微信开放平台
2、https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
在该用户下方选择生成带参数的二维码API
四、对应代码
(一)根据userId生成微信ticket
import com.alibaba.fastjson.JSONObject; import com.cyb.base.BaseResponse; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Api(tags = "根据userId生成微信二维码连接") public interface WeiXinQrCodeService { /** * 根据userId生成微信二维码连接 * * @param userId * @return */ @GetMapping("/getQrUrl") @ApiOperation(value = "根据userId生成微信二维码连接") BaseResponse<JSONObject> getQrUrl(@RequestParam("userId") Long userId); }
(二)实现类
import com.alibaba.fastjson.JSONObject; import com.cyb.base.BaseApiService; import com.cyb.base.BaseResponse; import com.cyb.weixin.api.service.WeiXinQrCodeService; import com.cyb.weixin.constant.WeiXinConstant; import com.cyb.weixin.mp.config.WxMpConfiguration; import com.cyb.weixin.mp.config.WxMpProperties; import me.chanjar.weixin.mp.api.WxMpQrcodeService; import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RestController; import java.net.URLEncoder; @RestController public class WeiXinQrCodeServiceImpl extends BaseApiService implements WeiXinQrCodeService { @Autowired private WxMpProperties wxMpProperties; @Override public BaseResponse<JSONObject> getQrUrl(Long userId) { if (userId == null) { return setResultError("userId不能为空!"); } //获取配置第一个appId,因为配置文件中可以配置多个APPID String appId = wxMpProperties.getConfigs().get(0).getAppId(); // 根据appid 获取对应的 WxMpQrcodeService WxMpQrcodeService qrcodeService = WxMpConfiguration.getMpServices().get(appId).getQrcodeService(); try { WxMpQrCodeTicket wxMpQrCodeTicket = qrcodeService.qrCodeCreateTmpTicket(userId + "", WeiXinConstant.QR_CODE_EXPIRE_SECONDS); if (wxMpQrCodeTicket == null) { return setResultError("生成二维码链接失败!"); } String ticket = wxMpQrCodeTicket.getTicket(); return setResultSuccess(URLEncoder.encode(ticket,"UTF-8")); } catch (Exception e) { return setResultError("生成二维码链接失败!"); } } }
(三)获取配置文件信息工具类 ,以下是获取配置文件中前缀为:wx.mp开头配置文件
@Data @ConfigurationProperties(prefix = "wx.mp") public class WxMpProperties { private List<MpConfig> configs; @Data public static class MpConfig { /** * 设置微信公众号的appid */ private String appId; /** * 设置微信公众号的app secret */ private String secret; /** * 设置微信公众号的token */ private String token; /** * 设置微信公众号的EncodingAESKey */ private String aesKey; } @Override public String toString() { return JsonUtils.toJson(this); } }
(四)配置文件bootstrap.yml 增加以下配置
wx: mp: configs: - appId: wx6abbebaf087d74c6 secret: ef98ffacbf90aa0e859a21a84a925 token: cybtoken
(五)WxMpConfiguration配置类
import com.cyb.weixin.mp.handler.*; import com.google.common.collect.Maps; import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; import me.chanjar.weixin.mp.api.WxMpMessageRouter; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.constant.WxMpEventConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static me.chanjar.weixin.common.api.WxConsts.*; /** * wechat mp configuration * * @author Binary Wang(https://github.com/binarywang) */ @Configuration @EnableConfigurationProperties(WxMpProperties.class) public class WxMpConfiguration { private LogHandler logHandler; private NullHandler nullHandler; private KfSessionHandler kfSessionHandler; private StoreCheckNotifyHandler storeCheckNotifyHandler; private LocationHandler locationHandler; private MenuHandler menuHandler; private MsgHandler msgHandler; private UnsubscribeHandler unsubscribeHandler; private SubscribeHandler subscribeHandler; private ScanHandler scanHandler; private WxMpProperties properties; private static Map<String, WxMpMessageRouter> routers = Maps.newHashMap(); private static Map<String, WxMpService> mpServices = Maps.newHashMap(); @Autowired public WxMpConfiguration(LogHandler logHandler, NullHandler nullHandler, KfSessionHandler kfSessionHandler, StoreCheckNotifyHandler storeCheckNotifyHandler, LocationHandler locationHandler, MenuHandler menuHandler, MsgHandler msgHandler, UnsubscribeHandler unsubscribeHandler, SubscribeHandler subscribeHandler, ScanHandler scanHandler, WxMpProperties properties) { this.logHandler = logHandler; this.nullHandler = nullHandler; this.kfSessionHandler = kfSessionHandler; this.storeCheckNotifyHandler = storeCheckNotifyHandler; this.locationHandler = locationHandler; this.menuHandler = menuHandler; this.msgHandler = msgHandler; this.unsubscribeHandler = unsubscribeHandler; this.subscribeHandler = subscribeHandler; this.scanHandler = scanHandler; this.properties = properties; } public static Map<String, WxMpMessageRouter> getRouters() { return routers; } public static Map<String, WxMpService> getMpServices() { return mpServices; } @Bean public Object services() { // 代码里getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!! final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs(); if (configs == null) { throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); } mpServices = configs.stream().map(a -> { WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage(); configStorage.setAppId(a.getAppId()); configStorage.setSecret(a.getSecret()); configStorage.setToken(a.getToken()); configStorage.setAesKey(a.getAesKey()); WxMpService service = new WxMpServiceImpl(); service.setWxMpConfigStorage(configStorage); routers.put(a.getAppId(), this.newRouter(service)); return service; }).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a, (o, n) -> o)); return Boolean.TRUE; } private WxMpMessageRouter newRouter(WxMpService wxMpService) { final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); // 记录所有事件的日志 (异步执行) newRouter.rule().handler(this.logHandler).next(); // 接收客服会话管理事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) .handler(this.kfSessionHandler).end(); newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) .handler(this.kfSessionHandler) .end(); newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) .handler(this.kfSessionHandler).end(); // 门店审核事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(WxMpEventConstants.POI_CHECK_NOTIFY) .handler(this.storeCheckNotifyHandler).end(); // 自定义菜单事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(MenuButtonType.CLICK).handler(this.menuHandler).end(); // 点击菜单连接事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(MenuButtonType.VIEW).handler(this.nullHandler).end(); // 关注事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(EventType.SUBSCRIBE).handler(this.subscribeHandler) .end(); // 取消关注事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(EventType.UNSUBSCRIBE) .handler(this.unsubscribeHandler).end(); // 上报地理位置事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(EventType.LOCATION).handler(this.locationHandler) .end(); // 接收地理位置消息 newRouter.rule().async(false).msgType(XmlMsgType.LOCATION) .handler(this.locationHandler).end(); // 扫码事件 newRouter.rule().async(false).msgType(XmlMsgType.EVENT) .event(EventType.SCAN).handler(this.scanHandler).end(); // 默认 newRouter.rule().async(false).handler(this.msgHandler).end(); return newRouter; } }
(六)微信常量类WeiXinConstant
package com.cyb.weixin.constant; /* * 微信常量 * @Author 陈远波 * @Date 2020-03-25 */ public interface WeiXinConstant { /** * 最大不超过2592000(即30天) */ Integer QR_CODE_EXPIRE_SECONDS = 2592000; }
(七)拼接二维码链接
(八)扫码handler
package com.cyb.weixin.mp.handler; import com.cyb.weixin.impl.manager.WxMpServiceManage; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Map; /** * @author Binary Wang(https://github.com/binarywang) */ @Component public class ScanHandler extends AbstractHandler { @Autowired private WxMpServiceManage wxMpServiceManage; @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> map, WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { // 扫码事件处理 String eventKey = wxMpXmlMessage.getEventKey(); if (!StringUtils.isEmpty(eventKey)) { Long userId = Long.parseLong(eventKey); String openId = wxMpXmlMessage.getFromUser(); wxMpServiceManage.handler(userId, openId); } return null; } }
(九)微信管理层
@Component public class WxMpServiceManage { @Autowired private MemberInfoServiceFeign memberInfoServiceFeign; public WxMpXmlOutMessage handler(Long userId, String openId) { // 先根据openid 查询是否已经关联 ---会员服务 BaseResponse<UserRespDto> userRespDtoBaseResponse = memberInfoServiceFeign.selectByOpenId(openId); if (userRespDtoBaseResponse.getCode().equals(HttpConstant.RPC_RESULT_SUCCESS)) { return null; } // 如果没有关注过的情况下 update ---会员服务 memberInfoServiceFeign.updateUseOpenId(userId,openId); return null; } }
(十)会员服务feign客户端
@FeignClient("cyb-member") public interface MemberInfoServiceFeign extends MemberInfoService { }
根据用户id和openID去数据库中查询是否有数据,如果有则代码已关注过,没有则代表未关注,该代码省略
(十一)关注handler
package com.cyb.weixin.mp.handler; import com.cyb.weixin.mp.builder.TextBuilder; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; import me.chanjar.weixin.mp.bean.result.WxMpUser; import org.springframework.stereotype.Component; import java.util.Map; /* * *关注 * @author Binary Wang(https://github.com/binarywang) */ @Component public class SubscribeHandler extends AbstractHandler { @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService weixinService, WxSessionManager sessionManager) throws WxErrorException { this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser()); // 获取微信用户基本信息 try { WxMpUser userWxInfo = weixinService.getUserService() .userInfo(wxMessage.getFromUser(), null); if (userWxInfo != null) { // TODO 可以添加关注用户到本地数据库 } } catch (WxErrorException e) { if (e.getError().getErrorCode() == 48001) { this.logger.info("该公众号没有获取用户信息权限!"); } } WxMpXmlOutMessage responseResult = null; try { responseResult = this.handleSpecial(wxMessage); } catch (Exception e) { this.logger.error(e.getMessage(), e); } if (responseResult != null) { return responseResult; } try { return new TextBuilder().build("感谢关注", wxMessage, weixinService); } catch (Exception e) { this.logger.error(e.getMessage(), e); } return null; } /** * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 */ private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage) throws Exception { //TODO return null; } }
(十二)取消关注handler
package com.cyb.weixin.mp.handler; import com.cyb.weixin.impl.feign.MemberInfoServiceFeign; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Map; /** * @author Binary Wang(https://github.com/binarywang) */ @Component public class UnsubscribeHandler extends AbstractHandler { @Autowired private MemberInfoServiceFeign memberInfoServiceFeign; @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) { String openId = wxMessage.getFromUser(); this.logger.info("取消关注用户 OPENID: " + openId); // TODO 可以更新本地数据库为取消关注状态 memberInfoServiceFeign.cancelFollowOpenId(openId); return null; } }
以上代码本人亲测有效,如有以为可以留言。
本人该篇博客主要用于本人以后查看开发思路以及帮助对该资料有需求的同志,并该博客有以下参考相关人员:余胜军,请大家在转载的时候也说明出处。