一、掃碼開發步驟
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; } }
以上代碼本人親測有效,如有以為可以留言。
本人該篇博客主要用於本人以后查看開發思路以及幫助對該資料有需求的同志,並該博客有以下參考相關人員:余勝軍,請大家在轉載的時候也說明出處。
