Ver Fonte

微信公众号绑定

tanbin há 1 ano atrás
pai
commit
8dfaba04e8

+ 104 - 0
sys-api-service/src/main/java/com/xy/config/WeChatOfficialConfig.java

@@ -0,0 +1,104 @@
+package com.xy.config;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+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.config.impl.WxMpDefaultConfigImpl;
+import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+@Component
+@RequiredArgsConstructor
+@Data
+public class WeChatOfficialConfig {
+    public static final String ENCRYPT_TYPE = "encrypt_type";
+    public static final String RAW = "raw";
+    public static final String AES = "aes";
+    public static final String SIGNATURE = "signature";
+    public static final String ECHO_STR = "echostr";
+    public static final String NONCE = "nonce";
+    public static final String TIMESTAMP = "timestamp";
+
+    /**
+     * 二维码场景值的前缀
+     */
+    public static final String QR_CODE_SCENE_PREFIX = "qrscene_";
+
+    private static final String APP_ID = "wx95d729fe985ef9de";
+    private static final String SECRET = "e76dffd8329ad6c5943cc5e1f6ff95f2";
+    private static final String TOKEN = "111";
+    private static final String AES_KEY = "111";
+    public static final String MSG_SIGNATURE = "msg_signature";
+    public static final String SCAN_HANDLER = "scanHandler";
+    public static final String SUBSCRIBE_HANDLER = "subscribeHandler";
+    public static final String UNSUBSCRIBE_HANDLER = "unSubscribeHandler";
+
+    /**
+     * 微信服务号 登录 相关对象
+     */
+    private WxMpService officialAccountLoginService;
+    private WxMpDefaultConfigImpl config;
+    private WxMpMessageRouter wxMpMessageRouter;
+
+    @Autowired
+    private Map<String, WxMpMessageHandler> wxMpMessageHandlers;
+
+
+    @PostConstruct
+    private void init() {
+        officialAccountLoginService = getWxMpService();
+        initConfig();
+        initRouter();
+    }
+
+    /**
+     * 初始化微信服务号
+     *
+     * @return {@link WxMpService}
+     */
+    public WxMpService getWxMpService() {
+        WxMpService service = new WxMpServiceImpl();
+        WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();
+        configStorage.setAppId(APP_ID);
+        configStorage.setSecret(SECRET);
+        configStorage.setToken(TOKEN);
+        configStorage.setAesKey(AES_KEY);
+        service.setWxMpConfigStorage(configStorage);
+        return service;
+    }
+
+    /**
+     * 初始化路由器
+     * 扫码、关注、取消关注
+     */
+    private void initRouter() {
+        wxMpMessageRouter = new WxMpMessageRouter(officialAccountLoginService);
+        wxMpMessageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.SUBSCRIBE).handler(wxMpMessageHandlers.get(SUBSCRIBE_HANDLER)).end();
+        wxMpMessageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.SCAN).handler(wxMpMessageHandlers.get(SCAN_HANDLER)).end();
+        wxMpMessageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.UNSUBSCRIBE).handler(wxMpMessageHandlers.get(UNSUBSCRIBE_HANDLER)).end();
+    }
+
+    /**
+     * 初始化配置信息
+     */
+    private void initConfig() {
+        config = new WxMpDefaultConfigImpl();
+        config.setAppId(APP_ID);
+        config.setToken(TOKEN);
+        config.setSecret(SECRET);
+    }
+
+}

+ 35 - 0
sys-api-service/src/main/java/com/xy/handler/ScanHandler.java

@@ -0,0 +1,35 @@
+package com.xy.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+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.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * 微信服务号扫码处理器
+ */
+@Component("scanHandler")
+@Slf4j
+public class ScanHandler implements WxMpMessageHandler {
+
+    /**
+     * 扫码事件 (暂不实现逻辑)
+     *
+     * @param wxMessage
+     * @param context
+     * @param wxMpService
+     * @param sessionManager
+     * @return
+     */
+    @Override
+    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) {
+        return WxMpXmlOutMessage.TEXT().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
+                .content("扫一扫关注公众号").build();
+    }
+
+}

+ 58 - 0
sys-api-service/src/main/java/com/xy/handler/SubscribeHandler.java

@@ -0,0 +1,58 @@
+package com.xy.handler;
+
+import com.alibaba.fastjson.JSON;
+import com.google.common.base.Throwables;
+
+import com.xy.config.WeChatOfficialConfig;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+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.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * 微信服务号 关注 事件 处理器
+ * 将eventKey 存储在 redis
+ *
+ * @author 谭斌
+ * @date 2024/02/21
+ */
+@Component("subscribeHandler")
+@Slf4j
+public class SubscribeHandler implements WxMpMessageHandler {
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    /**
+     * 拿到场景值和用户信息,写入到redis
+     *
+     * @param wxMessage
+     * @param context
+     * @param wxMpService
+     * @param sessionManager
+     * @return
+     */
+    @Override
+    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService
+            , WxSessionManager sessionManager) {
+        try {
+            WxMpUser user = wxMpService.getUserService().userInfo(wxMessage.getFromUser());
+            String eventKey = wxMessage.getEventKey().replaceAll(WeChatOfficialConfig.QR_CODE_SCENE_PREFIX, "");
+            redisTemplate.opsForValue().set(eventKey, JSON.toJSONString(user), 30, TimeUnit.DAYS);
+        } catch (Exception e) {
+            log.error("SubscribeHandler#handle fail:{}", Throwables.getStackTraceAsString(e));
+        }
+        return WxMpXmlOutMessage.TEXT().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
+                .content("感谢您的关注!").build();
+    }
+}

+ 29 - 0
sys-api-service/src/main/java/com/xy/handler/UnSubscribeHandler.java

@@ -0,0 +1,29 @@
+package com.xy.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+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.stereotype.Component;
+
+import java.util.Map;
+
+
+/**
+ * 取关
+ *
+ * @author 谭斌
+ * @date 2024/02/21
+ */
+@Component("unSubscribeHandler")
+@Slf4j
+public class UnSubscribeHandler implements WxMpMessageHandler {
+
+    @Override
+    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) {
+        return WxMpXmlOutMessage.TEXT().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
+                .content("我会想你的!").build();
+    }
+}

+ 164 - 0
sys-api-service/src/main/java/com/xy/service/UserInfoServiceImpl.java

@@ -1,31 +1,52 @@
 package com.xy.service;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.github.yitter.idgen.YitIdHelper;
+import com.google.common.base.Throwables;
 import com.xy.annotation.Lock;
+import com.xy.collections.list.JArrayList;
+import com.xy.collections.list.JList;
+import com.xy.config.WeChatOfficialConfig;
 import com.xy.config.WebMqttConfig;
+import com.xy.device.EnumDeviceQrCode;
+import com.xy.device.EnumDeviceType;
+import com.xy.dto.MercDeviceQrConfigDto;
 import com.xy.dto.SysWorkUser.AddDto;
 import com.xy.dto.SysWorkUser.DelDto;
 import com.xy.dto.SysWorkUser.UpdateDto;
 import com.xy.dto.UserInfoDto;
+import com.xy.dto.WxServiceMsgDto;
 import com.xy.entity.UserInfo;
+import com.xy.error.CommRuntimeException;
 import com.xy.mapper.UserInfoMapper;
 import com.xy.utils.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+import me.chanjar.weixin.mp.api.WxMpMessageRouter;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.List;
+import java.util.Map;
 
 import static com.xy.utils.Beans.copy;
 import static com.xy.utils.PlusBeans.toIPage;
@@ -50,6 +71,10 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
 
     private WebMqttConfig webMqttConfig;
 
+
+    private WeChatOfficialConfig weChatOfficialConfig;
+
+
     @Override
     @ApiOperation("集合查询")
     public R<List<UserInfoDto.Vo>> list(UserInfoDto.SelectListDto selectList) {
@@ -216,4 +241,143 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
         return R.ok();
     }
 
+
+    @SneakyThrows
+    @PostMapping("wxQrCode")
+    @ApiOperation("获取微信服务号二维码-扫码关注公众号并绑定")
+    public R<UserInfoDto.WxQrCodeVO> getQrCode(@RequestBody @Validated UserInfoDto.WxQrCode wxQrCode) {
+        WxMpService wxMpService = weChatOfficialConfig.getWxMpService();
+        if (wxMpService == null) {
+            throw new CommRuntimeException("获取微信公众号配置信息失败!");
+        }
+        //指定用户
+        String userId = wxQrCode.getUserId();
+        if (StrUtil.isEmpty(userId)) {
+            //当前登录用户
+            userId = AuthorizeUtils.getLoginId(String.class);
+        }
+        WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(userId, 2592000);
+        String tk = ticket.getTicket();
+        String url = wxMpService.getQrcodeService().qrCodePictureUrl(tk);
+        JList<String> contents = new JArrayList<>();
+        contents.set(url);
+        JList<String> text = new JArrayList<>();
+        text.set("使用微信[扫一扫]关注");
+        List<String> base64s = QRCodeUtils.create(contents, 400, 400, "back1.png", text).base64();
+        UserInfoDto.WxQrCodeVO vo = new UserInfoDto.WxQrCodeVO();
+        log.info("获取微信服务号二维码 usrId:{},tk:{}", userId, tk);
+        return R.ok(vo.setImgList(base64s).setTicket(tk));
+    }
+
+
+    @Autowired
+    private Map<String, WxMpMessageHandler> wxMpMessageHandlers;
+
+    /**
+     * 接收微信的事件消息
+     * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
+     *
+     * @return
+     */
+    @RequestMapping(value = "/receipt", produces = {"application/xml; charset=UTF-8"})
+    @ApiOperation("接收微信的事件消息")
+    public String receiptMessage(@RequestBody(required = false) WxServiceMsgDto wxServiceMsgDto, HttpServletRequest request) {
+
+        WxMpService wxMpService = weChatOfficialConfig.getWxMpService();
+        if (wxMpService == null) {
+            throw new CommRuntimeException("获取微信公众号配置信息失败!");
+        }
+        String echoStr = request.getParameter(WeChatOfficialConfig.ECHO_STR);
+        // echoStr!=null,说明只是微信调试的请求
+        if (CharSequenceUtil.isNotBlank(echoStr)) {
+            return echoStr;
+        }
+        String signature = request.getParameter(WeChatOfficialConfig.SIGNATURE);
+        String nonce = request.getParameter(WeChatOfficialConfig.NONCE);
+        String timestamp = request.getParameter(WeChatOfficialConfig.TIMESTAMP);
+        if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
+            throw new CommRuntimeException("接收微信的事件消息,验签异常!");
+        }
+        log.info("接收微信的事件消息:{}", wxServiceMsgDto);
+        //处理带参数请求
+        if (null != wxServiceMsgDto &&
+                !StrUtil.isEmpty(wxServiceMsgDto.getMsgType()) &&
+                !StrUtil.isEmpty(wxServiceMsgDto.getEvent()) &&
+                !StrUtil.isEmpty(wxServiceMsgDto.getEventKey()) && !wxServiceMsgDto.getEventKey().equals("null") &&
+                (wxServiceMsgDto.getEventKey().equals("subscribe") || wxServiceMsgDto.getEvent().equals("SCAN"))
+        ) {
+
+            //关注
+            if (wxServiceMsgDto.getEvent().equals("subscribe")) {
+                log.info("关注: " + wxServiceMsgDto.getFromUserName());
+                setCacheTicket("1", wxServiceMsgDto.getFromUserName());
+                if (wxServiceMsgDto.getEventKey().contains("FenXiangMa")) {
+                    log.info("优惠券领取");
+                }
+            }
+
+            //扫码
+            if (wxServiceMsgDto.getEvent().equals("SCAN")) {
+                log.info("扫码: " + wxServiceMsgDto.getFromUserName());
+                if (wxServiceMsgDto.getEventKey().contains("YaoQingMa")) {
+                    log.info("新用户邀请");
+                }
+            }
+
+            log.info("参数: " + wxServiceMsgDto.getEventKey());
+        }
+        return "";
+
+
+//
+//        String encryptType = CharSequenceUtil.isBlank(request.getParameter(WeChatOfficialConfig.ENCRYPT_TYPE)) ? WeChatOfficialConfig.RAW : request.getParameter(WeChatOfficialConfig.ENCRYPT_TYPE);
+//        if (WeChatOfficialConfig.RAW.equals(encryptType)) {
+//            WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
+//            log.info("raw inMessage:{}", JSON.toJSONString(inMessage));
+//            WxMpXmlOutMessage outMessage = weChatOfficialConfig.getWxMpMessageRouter().route(inMessage);
+//            return outMessage.toXml();
+//        } else if (WeChatOfficialConfig.AES.equals(encryptType)) {
+//            String msgSignature = request.getParameter(WeChatOfficialConfig.MSG_SIGNATURE);
+//            WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(request.getInputStream(), weChatOfficialConfig.getConfig(), timestamp, nonce, msgSignature);
+//            log.info("aes inMessage:{}", JSON.toJSONString(inMessage));
+//            WxMpXmlOutMessage outMessage = weChatOfficialConfig.getWxMpMessageRouter().route(inMessage);
+//            return outMessage.toEncryptedXml(weChatOfficialConfig.getConfig());
+//        }
+//        return "ok";
+
+
+    }
+
+    /**
+     * 缓存ticket
+     *
+     * @param tk
+     * @param openId
+     */
+    public static void setCacheTicket(String tk, String openId) {
+        RedisService<String> redisService = SpringBeanUtils.getBean(RedisService.class);
+        redisService.set("wechat:scan:ticket:" + tk, openId, 3600);//1h
+    }
+
+    /**
+     * 获取tk
+     *
+     * @param tk
+     * @return {@link String}
+     */
+    public static String getCacheTicket(String tk) {
+        RedisService<String> redisService = SpringBeanUtils.getBean(RedisService.class);
+        return redisService.get("wechat:scan:ticket:" + tk);
+    }
+
+    /**
+     * 刪除ticket
+     *
+     * @param tk
+     */
+    public static void delCacheTicket(String tk) {
+        RedisService<String> redisService = SpringBeanUtils.getBean(RedisService.class);
+        redisService.remove("wechat:scan:ticket:" + tk);
+    }
+
 }

+ 18 - 0
sys-api/src/main/java/com/xy/dto/UserInfoDto.java

@@ -269,4 +269,22 @@ public class UserInfoDto {
         private String aliUserId;
 
     }
+
+    @Data
+    @Accessors(chain = true)
+    public static class WxQrCode {
+        //        @NotBlank(message = "用户ID不能为空")
+        @ApiModelProperty(value = "id")
+        private String userId;
+    }
+
+    @Data
+    @Accessors(chain = true)
+    public static class WxQrCodeVO {
+        @ApiModelProperty(value = "imgList")
+        private List<String> imgList;
+
+        @ApiModelProperty(value = "ticket")
+        private String ticket;
+    }
 }

+ 45 - 0
sys-api/src/main/java/com/xy/dto/WxServiceMsgDto.java

@@ -0,0 +1,45 @@
+package com.xy.dto;
+
+import lombok.Data;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * 公众号返回参数封装
+ */
+@XmlRootElement(name = "xml")
+@XmlAccessorType(XmlAccessType.FIELD)
+@Data
+public class WxServiceMsgDto {
+
+    @XmlElement(name = "事件类型,scancode_push")
+    private String event;
+
+    @XmlElement(name = "Content")
+    private String content;
+
+    @XmlElement(name = "消息类型,event")
+    private String msgType;
+
+    @XmlElement(name = "扫描类型,一般是qrcode")
+    private String ScanType;
+
+    @XmlElement(name = "开发者 微信号")
+    private String toUserName;
+
+    @XmlElement(name = "事件KEY值,由开发者在创建菜单时设定")
+    private String eventKey;
+
+    @XmlElement(name = "CreateTime")
+    private String createTime;
+
+    /**
+     * fromUserName为关注人的openId
+     **/
+    @XmlElement(name = "FromUserName")
+    private String fromUserName;
+
+}