Bläddra i källkod

add 多公众号消息推送

zoujiajian 3 år sedan
förälder
incheckning
3dab857397

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/gzh/UserGzhRelationMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.gzh;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.UserGzhRelation;
+
+/*
+ *项目名: netflix
+ *文件名: UserGzhRelationMapper
+ *创建者: JavaZou
+ *创建时间:2023/1/16 10:58
+ */
+public interface UserGzhRelationMapper extends BaseMapper<UserGzhRelation> {
+}

+ 157 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/MsgEvent.java

@@ -0,0 +1,157 @@
+package com.cyksj.model.dto;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * @author valor.
+ * @date 2019-08-02 12:53
+ */
+@Getter
+@Setter
+@JacksonXmlRootElement(localName = "xml")
+public class MsgEvent {
+
+    /**
+     * 开发者微信号
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "ToUserName")
+    private String toUserName;
+
+    /**
+     * 发送方帐号 (一个OpenID)
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "FromUserName")
+    private String fromUserName;
+
+    /**
+     * 消息创建时间 (整型)
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "CreateTime")
+    private Long createTime;
+
+    /**
+     * 消息类型
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "MsgType")
+    private MsgType msgType;
+
+    /**
+     * 当消息为event时 触发的事件类型
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "Event")
+    private Event event;
+
+    /**
+     * 事件KEY值
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "EventKey")
+    private String eventKey;
+
+    /**
+     * 二维码扫码时 会出现该参数
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "Ticket")
+    private String ticket;
+
+    /**
+     * 当消息为text时 消息内容
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "Content")
+    private String content;
+
+    /**
+     * 图文消息个数
+     *   当用户发送文本、图片、视频、图文、地理位置这五种消息时,开发者只能回复1条图文消息
+     *   其余场景最多可回复8条图文消息
+     */
+    @JacksonXmlCData
+    @JacksonXmlProperty(localName = "ArticleCount")
+    private Integer articleCount;
+
+    /**
+     * 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
+     */
+    @JacksonXmlProperty(localName = "item")
+    @JacksonXmlElementWrapper(localName = "Articles")
+    private List<Article> articles;
+
+    public enum MsgType {
+        /**
+         * 消息类型
+         */
+        event("事件消息"),
+        text("文本消息"),
+        news("图文消息"),
+        image("图片消息"),
+        voice("语音消息"),
+        video("视频消息"),
+        shortvideo("小视频消息"),
+        location("地理位置消息"),
+        link("链接消息");
+
+        MsgType(String desc) { }
+    }
+
+    public enum Event {
+        /**
+         * 当消息为event时 可能是下列事件
+         */
+        subscribe("订阅"),
+        unsubscribe("取消订阅"),
+        SCAN("订阅状态中, 扫码二维码"),
+        LOCATION("上报地理位置"),
+        CLICK("点击自定义菜单(拉取子菜单 微信不会调用接口)"),
+        VIEW("点击菜单跳转链接"),
+        TEMPLATESENDJOBFINISH("模板消息"),
+        view_miniprogram("点击菜单跳转小程序");
+
+        Event(String desc) { }
+    }
+
+    @Getter
+    @Setter
+    public static class Article {
+        /**
+         * 扫码 图文消息标题
+         */
+        @JacksonXmlCData
+        @JacksonXmlProperty(localName = "Title")
+        private String title;
+
+        /**
+         * 扫码 图文消息描述
+         */
+        @JacksonXmlCData
+        @JacksonXmlProperty(localName = "Description")
+        private String desc;
+
+        /**
+         * 扫码 图文消息图片链接
+         */
+        @JacksonXmlCData
+        @JacksonXmlProperty(localName = "PicUrl")
+        private String picUrl;
+
+        /**
+         * 扫码 图文消息跳转链接
+         */
+        @JacksonXmlCData
+        @JacksonXmlProperty(localName = "Url")
+        private String link;
+    }
+}

+ 35 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/UserGzhRelation.java

@@ -0,0 +1,35 @@
+package com.cyksj.model.entity;
+
+import com.cyksj.model.dto.MsgEvent;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: UserGzhRelation
+ *创建者: JavaZou
+ *创建时间:2023/1/16 10:56
+ */
+@Getter
+@Setter
+public class UserGzhRelation extends BaseEntity{
+	private Long wxAppId;
+
+	private Long userId;
+
+	private String openId;
+
+	private String unionid;
+
+	private MsgEvent.Event event;
+
+	/**
+	 * 是否关注
+	 */
+	private Boolean isSubscribe;
+
+	/**
+	 * 是否是扫描二维码
+	 */
+	private Boolean isQrScene;
+}

+ 13 - 0
netflix-service/src/main/java/com/cyksj/service/gzh/GzhService.java

@@ -0,0 +1,13 @@
+package com.cyksj.service.gzh;
+
+import com.cyksj.model.dto.MsgEvent;
+
+/*
+ *项目名: netflix
+ *文件名: GzhService
+ *创建者: JavaZou
+ *创建时间:2023/1/13 17:09
+ */
+public interface GzhService {
+	String gzhMsg(MsgEvent msgEvent);
+}

+ 128 - 0
netflix-service/src/main/java/com/cyksj/service/gzh/factory/EventMsgService.java

@@ -0,0 +1,128 @@
+package com.cyksj.service.gzh.factory;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.mapper.UserMapper;
+import com.cyksj.mapper.WxAppMapper;
+import com.cyksj.mapper.gzh.UserGzhRelationMapper;
+import com.cyksj.model.dto.GzhUnionidUserinfo;
+import com.cyksj.model.dto.MsgEvent;
+import com.cyksj.model.entity.User;
+import com.cyksj.model.entity.UserGzhRelation;
+import com.cyksj.model.entity.WxApp;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.wechat.WeChatService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/*
+ *项目名: netflix
+ *文件名: EventMsgService
+ *创建者: JavaZou
+ *创建时间:2023/1/13 18:16
+ */
+@Service("event")
+@RequiredArgsConstructor
+@Slf4j
+public class EventMsgService implements GzhMsgService {
+	private final UserMapper userMapper;
+
+	private final UserGzhRelationMapper userGzhRelationMapper;
+
+	private final WeChatService weChatService;
+
+	private final WxAppMapper wxAppMapper;
+
+	private final RedisService redisService;
+
+	@Override
+	public String handleNotify(MsgEvent msgEvent) {
+		try {
+			if (msgEvent.getEvent() == MsgEvent.Event.subscribe) {
+				if (!saveOrGetUser(msgEvent)) {
+					return "";
+				}
+			} else if (msgEvent.getEvent() == MsgEvent.Event.unsubscribe) {
+				UserGzhRelation userGzhRelation = userGzhRelationMapper.selectOne(Wrappers.lambdaQuery(UserGzhRelation.class)
+						.eq(UserGzhRelation::getOpenId, msgEvent.getFromUserName()).last("limit 1"));
+				if (userGzhRelation == null) {
+					return "";
+				}
+				userGzhRelation.setIsSubscribe(false);
+				userGzhRelationMapper.updateById(userGzhRelation);
+			} else if (msgEvent.getEvent() == MsgEvent.Event.SCAN) {
+				if (!saveOrGetUser(msgEvent)) {
+					return "";
+				}
+			}
+			return "";
+		} catch (Exception e) {
+			log.error("接收微信公众:{},报错:{]", msgEvent.getToUserName(), e == null ? "" : e.getMessage());
+			return "";
+		}
+	}
+
+	public Boolean saveOrGetUser(MsgEvent msgEvent) throws Exception {
+		//扫描带参数二维码 场景值
+		String sceneKey = msgEvent.getEventKey();
+
+		String openId = msgEvent.getFromUserName();
+		if (StrUtil.isEmpty(openId)) {
+			return false;
+		}
+		//该用户是否存在于用户表里面
+		User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
+				.eq(User::getOpenId, openId)
+				.select(User::getId)
+				.last("limit 1"));
+		GzhUnionidUserinfo info = weChatService.getUserInfo2(weChatService.getAccessToken(), openId);
+		if (user == null) {
+			user = new User();
+			user.setNickname(info.getNickname())
+					.setSex(info.getSex())
+					.setCountry(info.getCountry())
+					.setProvince(info.getProvince())
+					.setCity(info.getCity())
+					.setHeadimgurl(info.getHeadimgurl());
+			user.setOpenId(openId);
+			user.setUnionid(info.getUnionid());
+			userMapper.insert(user);
+
+			saveGzhUserRelation(msgEvent, user.getId(), openId, info.getUnionid());
+		} else {
+			//之前关注过
+			if (user.getUnionid() == null || !user.getUnionid().equals(info.getUnionid())) {
+				user.setUnionid(info.getUnionid());
+				userMapper.updateById(user);
+			}
+			UserGzhRelation userGzhRelation = userGzhRelationMapper.selectOne(Wrappers.lambdaQuery(UserGzhRelation.class)
+					.eq(UserGzhRelation::getOpenId, msgEvent.getFromUserName()).last("limit 1"));
+			if (userGzhRelation == null) {
+				saveGzhUserRelation(msgEvent, user.getId(), openId, info.getUnionid());
+			}
+		}
+		//场景值扫描公众号二维码 登录
+		if (StrUtil.isNotEmpty(sceneKey)) {
+			redisService.set(RedisService.key.WX_GZH_QRCODE_LOGIN.getNameFormat(sceneKey), user.getId(), RedisService.key.WX_GZH_QRCODE_LOGIN.getTimeout());
+		}
+		return true;
+	}
+
+	private void saveGzhUserRelation(MsgEvent msgEvent, Long userId, String openId, String unionid) {
+		UserGzhRelation userGzhRelation = new UserGzhRelation();
+		userGzhRelation.setUserId(userId);
+
+		WxApp wxApp = wxAppMapper.selectOne(Wrappers.lambdaQuery(WxApp.class)
+				.eq(WxApp::getAppId, msgEvent.getToUserName()).last("limit 1"));
+		userGzhRelation.setWxAppId(wxApp.getId());
+		userGzhRelation.setIsSubscribe(true);
+		userGzhRelation.setEvent(msgEvent.getEvent());
+		userGzhRelation.setOpenId(openId);
+		userGzhRelation.setUnionid(unionid);
+		if (StrUtil.isNotEmpty(msgEvent.getEventKey())) {
+			userGzhRelation.setIsQrScene(true);
+		}
+		userGzhRelationMapper.insert(userGzhRelation);
+	}
+}

+ 13 - 0
netflix-service/src/main/java/com/cyksj/service/gzh/factory/GzhMsgService.java

@@ -0,0 +1,13 @@
+package com.cyksj.service.gzh.factory;
+
+import com.cyksj.model.dto.MsgEvent;
+
+/*
+ *项目名: netflix
+ *文件名: GzhMsgService
+ *创建者: JavaZou
+ *创建时间:2023/1/13 18:22
+ */
+public interface GzhMsgService{
+	String handleNotify(MsgEvent msgEvent);
+}

+ 28 - 0
netflix-service/src/main/java/com/cyksj/service/gzh/factory/GzhMsgServiceFactory.java

@@ -0,0 +1,28 @@
+package com.cyksj.service.gzh.factory;
+
+import com.cyksj.model.dto.MsgEvent;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/*
+ *项目名: netflix
+ *文件名: GzhMsgServiceFactory
+ *创建者: JavaZou
+ *创建时间:2023/1/13 18:16
+ */
+@Component
+public class GzhMsgServiceFactory {
+
+	@Resource
+	Map<String, GzhMsgService> ghzMsgServiceMap = new ConcurrentHashMap<>();
+
+
+	public String handleMsg(MsgEvent msgEvent) {
+		GzhMsgService gzhMsgService = ghzMsgServiceMap.get(msgEvent.getMsgType().name());
+
+		return gzhMsgService.handleNotify(msgEvent);
+	}
+}

+ 20 - 0
netflix-service/src/main/java/com/cyksj/service/gzh/factory/TextMsgService.java

@@ -0,0 +1,20 @@
+package com.cyksj.service.gzh.factory;
+
+import com.cyksj.model.dto.MsgEvent;
+import org.springframework.stereotype.Service;
+
+/*
+ *项目名: netflix
+ *文件名: TextMsgService
+ *创建者: JavaZou
+ *创建时间:2023/1/13 18:16
+ */
+@Service("text")
+public class TextMsgService implements GzhMsgService{
+
+	@Override
+	public String handleNotify(MsgEvent msgEvent) {
+
+		return "";
+	}
+}

+ 26 - 0
netflix-service/src/main/java/com/cyksj/service/gzh/impl/GzhServiceImpl.java

@@ -0,0 +1,26 @@
+package com.cyksj.service.gzh.impl;
+
+import com.cyksj.model.dto.MsgEvent;
+import com.cyksj.service.gzh.GzhService;
+import com.cyksj.service.gzh.factory.GzhMsgServiceFactory;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/*
+ *项目名: netflix
+ *文件名: GzhServiceImpl
+ *创建者: JavaZou
+ *创建时间:2023/1/13 17:09
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class GzhServiceImpl implements GzhService {
+	private final GzhMsgServiceFactory gzhMsgServiceFactory;
+
+	@Override
+	public String gzhMsg(MsgEvent msgEvent){
+		return gzhMsgServiceFactory.handleMsg(msgEvent);
+	}
+}

+ 68 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/gzh/GzhController.java

@@ -0,0 +1,68 @@
+package com.cyksj.web.controller.manage.gzh;
+
+import cn.hutool.core.util.StrUtil;
+import com.cyksj.common.util.IoKit;
+import com.cyksj.common.util.StringUtil;
+import com.cyksj.common.util.Xmls;
+import com.cyksj.model.dto.MsgEvent;
+import com.cyksj.service.gzh.GzhService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/*
+ *项目名: netflix
+ *文件名: GzhController
+ *创建者: JavaZou
+ *创建时间:2023/1/13 16:50
+ */
+@RestController
+@RequestMapping("/manage/gzh")
+@RequiredArgsConstructor
+@Slf4j
+public class GzhController {
+	private final HttpServletRequest request;
+
+	private final GzhService gzhService;
+
+	@RequestMapping(value = "/msg/notify", method = {RequestMethod.GET, RequestMethod.POST})
+	public String msgNotify(@RequestParam(value = "echostr", required = false) String echoStr) throws IOException {
+		if (StrUtil.endWithIgnoreCase(request.getMethod(), RequestMethod.GET.name())) {
+			if (StrUtil.isBlank(echoStr)) {
+				return StrUtil.EMPTY;
+			}
+			//验证消息推送URL是否正确
+			return echoStr;
+		}
+
+		String content = IoKit.toString(request.getInputStream(), IoKit.Charsets.UTF_8);
+		log.info("公众号消息推送通知: \n{}", content);
+
+		if (StringUtils.isBlank(content)) {
+			return StringUtil.EMPTY;
+		}
+		MsgEvent msgEvent;
+		try {
+			msgEvent = Xmls.parseObject(content, MsgEvent.class);
+		} catch (Exception e) {
+			log.warn(" ! 解析公众号推送XML 失败.");
+			return "";
+		}
+		if (null == msgEvent) {
+			return "";
+		}
+
+		if (msgEvent.getMsgType() == null) {
+			return "";
+		}
+
+		return gzhService.gzhMsg(msgEvent);
+	}
+}

+ 3 - 1
netflix-web/src/main/java/com/cyksj/web/controller/user/AuthorizationController.java

@@ -273,10 +273,12 @@ public class AuthorizationController {
      */
     @GetMapping("/wx/gzh/sceneStr/checkQrCode")
     public Result<String> checkQrCodeByGzhSceneStr(String sceneStr) throws Exception {
-        Long userId = (Long) redisService.get(RedisService.key.WX_GZH_QRCODE_LOGIN.getNameFormat(sceneStr));
+        String key = RedisService.key.WX_GZH_QRCODE_LOGIN.getNameFormat(sceneStr);
+        Long userId = (Long) redisService.get(key);
         if (userId == null) {
             return GatewayResponse.SUCCESS.newBuilder().setMsg("用户未扫码").toResult();
         }
+        redisService.del(key);
         User user = userService.getById(userId);
         StpUserUtil.login(userId);
         String token = StpUserUtil.getTokenValue();