feat: initial iShare project code
This commit is contained in:
18
pigx-visual/pigx-mp-platform/Dockerfile
Normal file
18
pigx-visual/pigx-mp-platform/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM pig4cloud/java:8-jre
|
||||
|
||||
MAINTAINER wangiegie@gmail.com
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom"
|
||||
|
||||
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
RUN mkdir -p /pigx-mp-platform
|
||||
|
||||
WORKDIR /pigx-mp-platform
|
||||
|
||||
EXPOSE 6000
|
||||
|
||||
ADD ./target/pigx-mp-platform.jar ./
|
||||
|
||||
CMD sleep 180;java $JAVA_OPTS -jar pigx-mp-platform.jar
|
||||
140
pigx-visual/pigx-mp-platform/pom.xml
Normal file
140
pigx-visual/pigx-mp-platform/pom.xml
Normal file
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-visual</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pigx-mp-platform</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>微信公众号管理模块</description>
|
||||
<properties>
|
||||
<mock.version>1.1.2</mock.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--注册中心客户端-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<!--配置中心客户端-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
<!--数据操作-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-data</artifactId>
|
||||
</dependency>
|
||||
<!--mybatis-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- mysql8 -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<!-- ojdbc8 -->
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
</dependency>
|
||||
<!--PG-->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<!-- druid 连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!--common-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-core</artifactId>
|
||||
</dependency>
|
||||
<!--安全模块-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-xss</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-log</artifactId>
|
||||
</dependency>
|
||||
<!-- sentinel-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-sentinel</artifactId>
|
||||
</dependency>
|
||||
<!--灰度支持-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-gray</artifactId>
|
||||
</dependency>
|
||||
<!--微信依赖-->
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-mp</artifactId>
|
||||
</dependency>
|
||||
<!--随机昵称生成-->
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>java-testdata-generator</artifactId>
|
||||
<version>${mock.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>guava</artifactId>
|
||||
<groupId>com.google.guava</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--web 模块-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--undertow容器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.mp;
|
||||
|
||||
import com.pig4cloud.pigx.common.feign.annotation.EnablePigxFeignClients;
|
||||
import com.pig4cloud.pigx.common.security.annotation.EnablePigxResourceServer;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019/03/25 微信公众号管理模块
|
||||
*/
|
||||
@EnablePigxFeignClients
|
||||
@EnablePigxResourceServer
|
||||
@EnableDiscoveryClient
|
||||
@SpringBootApplication
|
||||
public class PigxMpPlatformApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PigxMpPlatformApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.pig4cloud.pigx.mp.builder;
|
||||
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
public abstract class AbstractBuilder {
|
||||
|
||||
/**
|
||||
* 构建返回微信消息报文
|
||||
* @param content
|
||||
* @param wxMessage
|
||||
* @param service
|
||||
* @return
|
||||
*/
|
||||
public abstract WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, WxMpService service);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.pig4cloud.pigx.mp.builder;
|
||||
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutImageMessage;
|
||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
public class ImageBuilder extends AbstractBuilder {
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, WxMpService service) {
|
||||
|
||||
WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId(content).fromUser(wxMessage.getToUser())
|
||||
.toUser(wxMessage.getFromUser()).build();
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.pig4cloud.pigx.mp.builder;
|
||||
|
||||
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.message.WxMpXmlOutTextMessage;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
public class TextBuilder extends AbstractBuilder {
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, WxMpService service) {
|
||||
WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content).fromUser(wxMessage.getToUser())
|
||||
.toUser(wxMessage.getFromUser()).build();
|
||||
return m;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.mp.config;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019/03/27 微信上下文工具类
|
||||
*/
|
||||
@UtilityClass
|
||||
public class WxMpContextHolder {
|
||||
|
||||
private final ThreadLocal<String> THREAD_LOCAL_APPID = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* TTL 设置appId
|
||||
* @param appId
|
||||
*/
|
||||
public void setAppId(String appId) {
|
||||
THREAD_LOCAL_APPID.set(appId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TTL中的appId
|
||||
* @return
|
||||
*/
|
||||
public String getAppId() {
|
||||
return THREAD_LOCAL_APPID.get();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
THREAD_LOCAL_APPID.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, 云集汇通 All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the yunjihuitong.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.mp.config;
|
||||
|
||||
import me.chanjar.weixin.common.enums.TicketType;
|
||||
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019/03/26
|
||||
* <p>
|
||||
* 微信SDK redis扩展
|
||||
*/
|
||||
public class WxMpInRedisConfigStorage extends WxMpDefaultConfigImpl {
|
||||
|
||||
private final static String ACCESS_TOKEN_KEY = "wechat:access_token_";
|
||||
|
||||
private final static String JSAPI_TICKET_KEY = "wechat:jsapi_ticket_";
|
||||
|
||||
private final static String CARDAPI_TICKET_KEY = "wechat:cardapi_ticket_";
|
||||
|
||||
private final RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
private String accessTokenKey;
|
||||
|
||||
private String jsapiTicketKey;
|
||||
|
||||
private String cardapiTicketKey;
|
||||
|
||||
public WxMpInRedisConfigStorage(RedisTemplate redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 每个公众号生成独有的存储key
|
||||
* @param appId
|
||||
*/
|
||||
@Override
|
||||
public void setAppId(String appId) {
|
||||
super.setAppId(appId);
|
||||
this.accessTokenKey = ACCESS_TOKEN_KEY.concat(appId);
|
||||
this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(appId);
|
||||
this.cardapiTicketKey = CARDAPI_TICKET_KEY.concat(appId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return redisTemplate.opsForValue().get(this.accessTokenKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessTokenExpired() {
|
||||
return redisTemplate.getExpire(accessTokenKey) < 2L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
|
||||
redisTemplate.opsForValue().set(accessTokenKey, accessToken, expiresInSeconds - 200, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireAccessToken() {
|
||||
redisTemplate.expire(this.accessTokenKey, 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJsapiTicket() {
|
||||
return redisTemplate.opsForValue().get(this.jsapiTicketKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCardApiTicket() {
|
||||
return redisTemplate.opsForValue().get(cardapiTicketKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTicket(TicketType type) {
|
||||
return redisTemplate.opsForValue().get(getTicketRedisKey(type));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTicketExpired(TicketType type) {
|
||||
return redisTemplate.getExpire(this.getTicketRedisKey(type)) < 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateTicket(TicketType type, String jsapiTicket, int expiresInSeconds) {
|
||||
redisTemplate.opsForValue().set(getTicketRedisKey(type), jsapiTicket, expiresInSeconds - 200, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireTicket(TicketType type) {
|
||||
redisTemplate.expire(getTicketRedisKey(type), 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private String getTicketRedisKey(TicketType type) {
|
||||
return String.format("wechat:ticket:key:%s:%s", this.appId, type.getCode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package com.pig4cloud.pigx.mp.config;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteTenantService;
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.RetOps;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantBroker;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.handler.*;
|
||||
import com.pig4cloud.pigx.mp.service.WxAccountService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.boot.web.context.WebServerInitializedEvent;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static me.chanjar.weixin.common.api.WxConsts.*;
|
||||
|
||||
/**
|
||||
* @author Binary Wang
|
||||
* @author lengleng
|
||||
* <p>
|
||||
* 微信公众号基础配置,初始化配置
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class WxMpInitConfigRunner {
|
||||
|
||||
/**
|
||||
* 保存 appid-router 的对应关系
|
||||
*/
|
||||
private static Map<String, WxMpMessageRouter> routers = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* 保存 appid-mpservice 的对应关系
|
||||
*/
|
||||
private static Map<String, WxMpService> mpServices = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* 保存 appid-tenantId 的对应关系
|
||||
*/
|
||||
private static final Map<String, Long> tenants = Maps.newHashMap();
|
||||
|
||||
private final RemoteTenantService tenantService;
|
||||
|
||||
private final WxAccountService accountService;
|
||||
|
||||
private final LogHandler logHandler;
|
||||
|
||||
private final NullHandler nullHandler;
|
||||
|
||||
private final KfSessionHandler kfSessionHandler;
|
||||
|
||||
private final StoreCheckNotifyHandler storeCheckNotifyHandler;
|
||||
|
||||
private final LocationHandler locationHandler;
|
||||
|
||||
private final MenuHandler menuHandler;
|
||||
|
||||
private final MsgHandler msgHandler;
|
||||
|
||||
private final UnsubscribeHandler unsubscribeHandler;
|
||||
|
||||
private final SubscribeHandler subscribeHandler;
|
||||
|
||||
private final ScanHandler scanHandler;
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@Async
|
||||
@Order
|
||||
@EventListener({ WebServerInitializedEvent.class })
|
||||
public void WebServerInit() {
|
||||
this.initServices();
|
||||
}
|
||||
|
||||
public void initServices() {
|
||||
// 获取全部租户 遍历所有租户对应的公众号列表
|
||||
List<WxAccount> accountList = new ArrayList<>();
|
||||
// @formatter:off
|
||||
RetOps.of(tenantService.list(SecurityConstants.FROM_IN))
|
||||
.getData()
|
||||
.orElseGet(Collections::emptyList)
|
||||
.forEach(
|
||||
tenant -> TenantBroker.runAs(tenant.getId(), (id) -> accountList.addAll(accountService.list()))
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
mpServices = accountList.stream().map(a -> {
|
||||
WxMpInRedisConfigStorage configStorage = new WxMpInRedisConfigStorage(redisTemplate);
|
||||
configStorage.setAppId(a.getAppid());
|
||||
configStorage.setSecret(a.getAppsecret());
|
||||
configStorage.setToken(a.getToken());
|
||||
configStorage.setAesKey(a.getAeskey());
|
||||
|
||||
WxMpService service = new WxMpServiceImpl();
|
||||
service.setWxMpConfigStorage(configStorage);
|
||||
routers.put(a.getAppid(), this.newRouter(service));
|
||||
tenants.put(a.getAppid(), a.getTenantId());
|
||||
return service;
|
||||
}).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* redis 监听配置,监听 mp_redis_reload_topic,重新加载配置
|
||||
* @param redisConnectionFactory redis 配置
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(redisConnectionFactory);
|
||||
container.addMessageListener((message, bytes) -> {
|
||||
log.warn("接收到重新加载公众号配置事件");
|
||||
initServices();
|
||||
}, new ChannelTopic(CacheConstants.MP_REDIS_RELOAD_TOPIC));
|
||||
return container;
|
||||
}
|
||||
|
||||
public static Map<String, Long> getTenants() {
|
||||
return tenants;
|
||||
}
|
||||
|
||||
public static Map<String, WxMpMessageRouter> getRouters() {
|
||||
return routers;
|
||||
}
|
||||
|
||||
public static Map<String, WxMpService> getMpServices() {
|
||||
return mpServices;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pig4cloud.pigx.mp.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/25
|
||||
* <p>
|
||||
* 消息类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MsgTypeEnum {
|
||||
|
||||
/**
|
||||
* 用户发给公众号
|
||||
*/
|
||||
USER2MP("1", "用户发给公众号"),
|
||||
|
||||
/**
|
||||
* 公众号发给用户
|
||||
*/
|
||||
MP2USER("2", "公众号发给用户");
|
||||
|
||||
/***
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pig4cloud.pigx.mp.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/25
|
||||
* <p>
|
||||
* 匹配类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ReplyMateEnum {
|
||||
|
||||
/**
|
||||
* 完全匹配
|
||||
*/
|
||||
ALL("1", "完全匹配"),
|
||||
|
||||
/**
|
||||
* 半匹配
|
||||
*/
|
||||
LIKE("2", "半匹配");
|
||||
|
||||
/***
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.pig4cloud.pigx.mp.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/25
|
||||
* <p>
|
||||
* 回复类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ReplyTypeEnum {
|
||||
|
||||
/**
|
||||
* 关注自动回复
|
||||
*/
|
||||
ATTENTION("1", "关注时回复"),
|
||||
|
||||
/**
|
||||
* 用户消息回复
|
||||
*/
|
||||
MSG("2", "消息回复"),
|
||||
|
||||
/**
|
||||
* 关键字回复
|
||||
*/
|
||||
KEYWORD("3", "关键词回复");
|
||||
|
||||
/***
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 根据 type 查找枚举
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
public static ReplyTypeEnum getEnumByType(String type) {
|
||||
for (ReplyTypeEnum value : ReplyTypeEnum.values()) {
|
||||
if (value.getType().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pig4cloud.pigx.mp.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/4/26
|
||||
* <p>
|
||||
* 公众号订阅状态
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SubStatusEnum {
|
||||
|
||||
/**
|
||||
* 已关注
|
||||
*/
|
||||
SUBED("1", "已关注"),
|
||||
|
||||
/**
|
||||
* 未关注
|
||||
*/
|
||||
UNSUB("0", "关注");
|
||||
|
||||
/***
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.log.annotation.SysLog;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.service.WxAccountService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 公众号账户
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:07:53
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/wx-account")
|
||||
public class WxAccountController {
|
||||
|
||||
private final WxAccountService wxAccountService;
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
* @param wxAccount 公众号账户
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public R getWxAccountPage(Page page, WxAccount wxAccount) {
|
||||
LambdaQueryWrapper<WxAccount> wrapper = Wrappers.<WxAccount>lambdaQuery()
|
||||
.like(StrUtil.isNotBlank(wxAccount.getName()), WxAccount::getName, wxAccount.getName())
|
||||
.like(StrUtil.isNotBlank(wxAccount.getAccount()), WxAccount::getAccount, wxAccount.getAccount());
|
||||
return R.ok(wxAccountService.page(page, wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询公众号账户
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R getById(@PathVariable("id") Long id) {
|
||||
return R.ok(wxAccountService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增公众号账户
|
||||
* @param wxAccount 公众号账户
|
||||
* @return R
|
||||
*/
|
||||
@SysLog("新增公众号账户")
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccount_add')")
|
||||
public R save(@RequestBody WxAccount wxAccount) {
|
||||
wxAccountService.save(wxAccount);
|
||||
redisTemplate.convertAndSend(CacheConstants.MP_REDIS_RELOAD_TOPIC, "重新加载公众号配置");
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改公众号账户
|
||||
* @param wxAccount 公众号账户
|
||||
* @return R
|
||||
*/
|
||||
@SysLog("修改公众号账户")
|
||||
@PutMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccount_edit')")
|
||||
public R updateById(@RequestBody WxAccount wxAccount) {
|
||||
wxAccountService.updateById(wxAccount);
|
||||
redisTemplate.convertAndSend(CacheConstants.MP_REDIS_RELOAD_TOPIC, "重新加载公众号配置");
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除公众号账户
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@SysLog("删除公众号账户")
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccount_del')")
|
||||
public R removeById(@PathVariable Long id) {
|
||||
wxAccountService.removeById(id);
|
||||
redisTemplate.convertAndSend(CacheConstants.MP_REDIS_RELOAD_TOPIC, "重新加载公众号配置");
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成公众号二维码
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@SysLog("生成公众号二维码")
|
||||
@PostMapping("/qr/{appId}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccount_add')")
|
||||
public R qr(@PathVariable String appId) {
|
||||
return wxAccountService.generateQr(appId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公众号列表
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R list(String name) {
|
||||
LambdaQueryWrapper<WxAccount> wrapper = Wrappers.<WxAccount>lambdaQuery().like(StrUtil.isNotBlank(name),
|
||||
WxAccount::getName, name);
|
||||
return R.ok(wxAccountService.list(wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公众号接口数据
|
||||
* @param appId 公众号
|
||||
* @param interval 时间间隔
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/statistics")
|
||||
public R statistics(String appId, String interval) {
|
||||
return wxAccountService.statistics(appId, interval);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@PostMapping("/clear-quota/{appId}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccount_del')")
|
||||
public R clearQuota(@PathVariable String appId) {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
wxMpService.clearQuota(appId);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.log.annotation.SysLog;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.service.WxAccountFansService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 微信公众号粉丝管理
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:08:08
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/wx-account-fans")
|
||||
public class WxAccountFansController {
|
||||
|
||||
private final WxAccountFansService wxAccountFansService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
* @param wxAccountFans 微信公众号粉丝
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public R getWxAccountFansPage(Page page, WxAccountFans wxAccountFans) {
|
||||
return R.ok(wxAccountFansService.getFansWithTagPage(page, wxAccountFans));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询微信公众号粉丝
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R getById(@PathVariable("id") Long id) {
|
||||
return R.ok(wxAccountFansService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改微信公众号粉丝
|
||||
* @param wxAccountFans 微信公众号粉丝
|
||||
* @return R
|
||||
*/
|
||||
@SysLog("修改微信公众号粉丝")
|
||||
@PutMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccountfans_edit')")
|
||||
public R updateById(@RequestBody WxAccountFans wxAccountFans) {
|
||||
return R.ok(wxAccountFansService.updateFans(wxAccountFans));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除微信公众号粉丝
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@SysLog("删除微信公众号粉丝")
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccountfans_del')")
|
||||
public R removeById(@PathVariable Long id) {
|
||||
return R.ok(wxAccountFansService.removeById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步粉丝
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/sync/{appId}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxaccountfans_sync')")
|
||||
public R sync(@PathVariable String appId) {
|
||||
return R.ok(wxAccountFansService.syncAccountFans(appId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消拉黑
|
||||
* @param ids
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/unblack/{appId}")
|
||||
public R unblack(@RequestBody Long[] ids, @PathVariable String appId) {
|
||||
return R.ok(wxAccountFansService.unblack(ids, appId));
|
||||
}
|
||||
|
||||
@PostMapping("/black/{appId}")
|
||||
public R black(@RequestBody Long[] ids, @PathVariable String appId) {
|
||||
return R.ok(wxAccountFansService.black(ids, appId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountTag;
|
||||
import com.pig4cloud.pigx.mp.entity.dto.WxAccountTagDeleteDTO;
|
||||
import com.pig4cloud.pigx.mp.service.WxAccountTagService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 微信账户标签
|
||||
*
|
||||
* @author lishangbu
|
||||
* @date 2021/12/31
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/wx-account-tag")
|
||||
public class WxAccountTagController {
|
||||
|
||||
private final WxAccountTagService wxAccountTagService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
* @param wxAccountTag 账户标签查询条件
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public R<Page<WxAccountTag>> getWxAccountTagPage(Page<WxAccountTag> page, WxAccountTag wxAccountTag) {
|
||||
LambdaQueryWrapper<WxAccountTag> wrapper = Wrappers.<WxAccountTag>lambdaQuery()
|
||||
.like(StrUtil.isNotBlank(wxAccountTag.getTag()), WxAccountTag::getTag, wxAccountTag.getTag())
|
||||
.eq(StrUtil.isNotBlank(wxAccountTag.getWxAccountAppid()), WxAccountTag::getWxAccountAppid,
|
||||
wxAccountTag.getWxAccountAppid());
|
||||
return R.ok(wxAccountTagService.page(page, wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表查询
|
||||
* @param wxAccountTag 账户标签查询条件
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<List<WxAccountTag>> listWxAccountTags(WxAccountTag wxAccountTag) {
|
||||
return R.ok(wxAccountTagService.list(Wrappers.query(wxAccountTag)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存账户标签
|
||||
* @param wxAccountTag 待保存的账户标签
|
||||
* @return 包含保存成功后的账户标签的API调用结果
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wx_account_tag_add')")
|
||||
public R<WxAccountTag> saveAccountTag(@RequestBody @Valid WxAccountTag wxAccountTag) {
|
||||
return R.ok(wxAccountTagService.saveAccountTag(wxAccountTag));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改账户标签
|
||||
* @param wxAccountTag 待修改的账户标签
|
||||
* @return 包含修改成功后的账户标签的API调用结果
|
||||
*/
|
||||
@PutMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wx_account_tag_edit')")
|
||||
public R<WxAccountTag> updateAccountTag(@RequestBody @Valid WxAccountTag wxAccountTag) {
|
||||
return R.ok(wxAccountTagService.updateAccountTag(wxAccountTag));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param wxAccountTag 标签
|
||||
* @return R
|
||||
*/
|
||||
@DeleteMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wx_account_tag_del')")
|
||||
public R<Boolean> removeAccountTagById(@RequestBody WxAccountTagDeleteDTO deleteDTO) {
|
||||
|
||||
deleteDTO.getIds().forEach(item -> {
|
||||
WxAccountTag wxAccountTag = new WxAccountTag();
|
||||
wxAccountTag.setId(item);
|
||||
wxAccountTag.setWxAccountAppid(deleteDTO.getWxAccountAppid());
|
||||
wxAccountTagService.removeAccountTagById(wxAccountTag);
|
||||
});
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步粉丝
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/sync/{appId}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wx_account_tag_sync')")
|
||||
public R sync(@PathVariable String appId) {
|
||||
return R.ok(wxAccountTagService.syncAccountTags(appId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.constant.ReplyTypeEnum;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAutoReply;
|
||||
import com.pig4cloud.pigx.mp.service.WxAutoReplyService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息自动回复
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-04-18 15:40:39
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/wx-auto-reply")
|
||||
public class WxAutoReplyController {
|
||||
|
||||
private final WxAutoReplyService wxAutoReplyService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
* @param wxAutoReply 消息自动回复
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public R getWxAutoReplyPage(Page page, WxAutoReply wxAutoReply) {
|
||||
return R.ok(wxAutoReplyService.page(page, Wrappers.query(wxAutoReply)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询消息自动回复
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R getById(@PathVariable("id") Long id) {
|
||||
return R.ok(wxAutoReplyService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增消息自动回复
|
||||
* @param wxAutoReply 消息自动回复
|
||||
* @return R
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxautoreply_add')")
|
||||
public R save(@RequestBody WxAutoReply wxAutoReply) {
|
||||
this.jude(wxAutoReply);
|
||||
return R.ok(wxAutoReplyService.save(wxAutoReply));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改消息自动回复
|
||||
* @param wxAutoReply 消息自动回复
|
||||
* @return R
|
||||
*/
|
||||
@PutMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxautoreply_edit')")
|
||||
public R updateById(@RequestBody WxAutoReply wxAutoReply) {
|
||||
return R.ok(wxAutoReplyService.updateById(wxAutoReply));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除消息自动回复
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxautoreply_del')")
|
||||
public R removeById(@PathVariable Long id) {
|
||||
return R.ok(wxAutoReplyService.removeById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* //校验参数
|
||||
* @param wxAutoReply
|
||||
*/
|
||||
public void jude(WxAutoReply wxAutoReply) {
|
||||
if (ReplyTypeEnum.MSG.getType().equals(wxAutoReply.getType())) {
|
||||
Wrapper<WxAutoReply> queryWrapper = Wrappers.<WxAutoReply>lambdaQuery().eq(WxAutoReply::getReqType,
|
||||
wxAutoReply.getReqType());
|
||||
List<WxAutoReply> list = wxAutoReplyService.list(queryWrapper);
|
||||
if (wxAutoReply.getId() != null) {
|
||||
if (list != null && list.size() == 1) {
|
||||
if (!list.get(0).getId().equals(wxAutoReply.getId())) {
|
||||
throw new RuntimeException("请求消息类型重复");
|
||||
}
|
||||
}
|
||||
if (list != null && list.size() > 1) {
|
||||
throw new RuntimeException("请求消息类型重复");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ReplyTypeEnum.KEYWORD.getType().equals(wxAutoReply.getType())) {
|
||||
Wrapper<WxAutoReply> queryWrapper = Wrappers.<WxAutoReply>lambdaQuery()
|
||||
.eq(WxAutoReply::getReqKey, wxAutoReply.getReqKey())
|
||||
.eq(WxAutoReply::getRepType, wxAutoReply.getRepMate());
|
||||
List<WxAutoReply> list = wxAutoReplyService.list(queryWrapper);
|
||||
if (list != null && list.size() == 1) {
|
||||
if (!list.get(0).getId().equals(wxAutoReply.getId())) {
|
||||
throw new RuntimeException("关键词重复");
|
||||
}
|
||||
}
|
||||
if (list != null && list.size() > 1) {
|
||||
throw new RuntimeException("关键词重复");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMsg;
|
||||
import com.pig4cloud.pigx.mp.service.WxMsgService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 微信粉丝消息管理
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:27
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/wx-fans-msg")
|
||||
public class WxFansMsgController {
|
||||
|
||||
private final WxMsgService wxMsgService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
* @param msg 查询条件
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public R getWxMsgPage(Page page, WxMsg msg) {
|
||||
LambdaQueryWrapper<WxMsg> wrapper = Wrappers.<WxMsg>lambdaQuery()
|
||||
.eq(StrUtil.isNotBlank(msg.getAppId()), WxMsg::getAppId, msg.getAppId())
|
||||
.like(StrUtil.isNotBlank(msg.getNickName()), WxMsg::getNickName, msg.getNickName())
|
||||
.eq(StrUtil.isNotBlank(msg.getType()), WxMsg::getType, msg.getType());
|
||||
return R.ok(wxMsgService.page(page, wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询微信消息
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R getById(@PathVariable("id") Long id) {
|
||||
return R.ok(wxMsgService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增微信消息
|
||||
* @param wxMsg 微信消息
|
||||
* @return R
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmsg_add')")
|
||||
public R save(@RequestBody WxMsg wxMsg) {
|
||||
return wxMsgService.saveAndPushMsg(wxMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改微信消息
|
||||
* @param wxMsg 微信消息
|
||||
* @return R
|
||||
*/
|
||||
@PutMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmsg_edit')")
|
||||
public R updateById(@RequestBody WxMsg wxMsg) {
|
||||
return R.ok(wxMsgService.updateById(wxMsg));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除微信消息
|
||||
* @param id id
|
||||
* @return R
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmsg_del')")
|
||||
public R removeById(@PathVariable String id) {
|
||||
return R.ok(wxMsgService.removeById(id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.mp.api.WxMpDraftService;
|
||||
import me.chanjar.weixin.mp.api.WxMpMaterialService;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.draft.WxMpAddDraft;
|
||||
import me.chanjar.weixin.mp.bean.draft.WxMpDraftArticles;
|
||||
import me.chanjar.weixin.mp.bean.draft.WxMpUpdateDraft;
|
||||
import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult;
|
||||
import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
|
||||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialFileBatchGetResult;
|
||||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 微信素材
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-03-23 21:26:35
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/wx-material")
|
||||
public class WxMaterialController {
|
||||
|
||||
/**
|
||||
* 上传非图文微信素材
|
||||
* @param mulFile
|
||||
* @param mediaType
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/materialFileUpload")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmaterial_add')")
|
||||
public R materialFileUpload(@RequestPart("file") MultipartFile mulFile, @RequestParam String appId,
|
||||
@RequestParam("title") String title, @RequestParam("introduction") String introduction,
|
||||
@RequestParam("mediaType") String mediaType) {
|
||||
try {
|
||||
String originalFilename = new String(
|
||||
Objects.requireNonNull(mulFile.getOriginalFilename()).getBytes(StandardCharsets.ISO_8859_1),
|
||||
StandardCharsets.UTF_8);
|
||||
WxMpMaterial material = new WxMpMaterial();
|
||||
material.setName(originalFilename);
|
||||
if (WxConsts.MediaFileType.VIDEO.equals(mediaType)) {
|
||||
material.setVideoTitle(title);
|
||||
material.setVideoIntroduction(introduction);
|
||||
}
|
||||
|
||||
File file = FileUtil.newFile(FileUtil.getTmpDirPath() + originalFilename);
|
||||
mulFile.transferTo(file);
|
||||
material.setFile(file);
|
||||
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMaterialService wxMpMaterialService = wxMpService.getMaterialService();
|
||||
WxMpMaterialUploadResult wxMpMaterialUploadResult = wxMpMaterialService.materialFileUpload(mediaType,
|
||||
material);
|
||||
WxMpMaterialFileBatchGetResult.WxMaterialFileBatchGetNewsItem wxMpMaterialFileBatchGetResult = new WxMpMaterialFileBatchGetResult.WxMaterialFileBatchGetNewsItem();
|
||||
wxMpMaterialFileBatchGetResult.setName(file.getName());
|
||||
wxMpMaterialFileBatchGetResult.setMediaId(wxMpMaterialUploadResult.getMediaId());
|
||||
wxMpMaterialFileBatchGetResult.setUrl(wxMpMaterialUploadResult.getUrl());
|
||||
FileUtil.del(file);
|
||||
return R.ok(wxMpMaterialFileBatchGetResult);
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.warn("上传非图文微信素材失败: {}", e.getLocalizedMessage());
|
||||
return R.failed(e.getLocalizedMessage());
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("上传失败", e);
|
||||
return R.failed(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增图文消息
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/materialNews")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmaterial_add')")
|
||||
public R materialNewsUpload(@RequestBody JSONObject data) {
|
||||
try {
|
||||
JSONArray jSONArray = data.getJSONArray("articles");
|
||||
List<WxMpDraftArticles> articles = jSONArray.toList(WxMpDraftArticles.class);
|
||||
|
||||
WxMpAddDraft draft = new WxMpAddDraft();
|
||||
draft.setArticles(articles);
|
||||
String appId = data.getStr("appId");
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpDraftService draftService = wxMpService.getDraftService();
|
||||
return R.ok(draftService.addDraft(draft));
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.warn("新增图文失败: {}", e.getMessage());
|
||||
return R.failed(e.getMessage());
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("新增图文失败", e);
|
||||
return R.failed(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改图文消息
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
@PutMapping("/materialNews")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmaterial_add')")
|
||||
public R materialNewsUpdate(@RequestBody JSONObject data) {
|
||||
try {
|
||||
String mediaId = data.getStr("mediaId");
|
||||
JSONArray jSONArray = data.getJSONArray("articles");
|
||||
List<WxMpDraftArticles> articles = jSONArray.toList(WxMpDraftArticles.class);
|
||||
String appId = data.getStr("appId");
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpDraftService draftService = wxMpService.getDraftService();
|
||||
|
||||
WxMpUpdateDraft mpUpdateDraft = new WxMpUpdateDraft();
|
||||
mpUpdateDraft.setMediaId(mediaId);
|
||||
int index = 0;
|
||||
for (WxMpDraftArticles article : articles) {
|
||||
mpUpdateDraft.setIndex(index);
|
||||
mpUpdateDraft.setArticles(article);
|
||||
draftService.updateDraft(mpUpdateDraft);
|
||||
index++;
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.warn("修改图文失败:{}", e.getLocalizedMessage());
|
||||
return R.failed(e.getLocalizedMessage());
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("修改图文失败", e);
|
||||
return R.failed(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图文消息内的图片获取URL
|
||||
* @param mulFile
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/newsImgUpload")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmaterial_add')")
|
||||
public String newsImgUpload(@RequestParam("file") MultipartFile mulFile, @RequestParam String appId)
|
||||
throws Exception {
|
||||
File file = FileUtil.createTempFile(FileUtil.getTmpDir());
|
||||
mulFile.transferTo(file);
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMaterialService wxMpMaterialService = wxMpService.getMaterialService();
|
||||
WxMediaImgUploadResult wxMediaImgUploadResult = wxMpMaterialService.mediaImgUpload(file);
|
||||
Map<Object, Object> responseData = new HashMap<>(2);
|
||||
responseData.put("link", wxMediaImgUploadResult.getUrl());
|
||||
FileUtil.del(file);
|
||||
return JSONUtil.toJsonStr(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除微信素材
|
||||
* @param
|
||||
* @return R
|
||||
*/
|
||||
@DeleteMapping
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmaterial_del')")
|
||||
public R materialDel(String appId, String id) {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMaterialService wxMpMaterialService = wxMpService.getMaterialService();
|
||||
try {
|
||||
return R.ok(wxMpMaterialService.materialDelete(id));
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error("删除微信素材失败", e);
|
||||
return R.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public R getWxMaterialPage(Page page, String type, String appId) {
|
||||
try {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMaterialService wxMpMaterialService = wxMpService.getMaterialService();
|
||||
|
||||
int count = (int) page.getSize();
|
||||
int offset = (int) page.getCurrent() * count - count;
|
||||
if (WxConsts.MaterialType.NEWS.equals(type)) {
|
||||
WxMpDraftService draftService = wxMpService.getDraftService();
|
||||
return R.ok(draftService.listDraft(offset, count));
|
||||
}
|
||||
else {
|
||||
return R.ok(wxMpMaterialService.materialFileBatchGet(type, offset, count));
|
||||
}
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error("查询素材失败", e);
|
||||
return R.failed(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信视频素材
|
||||
* @param
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/materialVideo")
|
||||
public R getMaterialVideo(String mediaId, String appId) {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMaterialService wxMpMaterialService = wxMpService.getMaterialService();
|
||||
try {
|
||||
return R.ok(wxMpMaterialService.materialVideoInfo(mediaId));
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error("获取微信视频素材失败", e);
|
||||
return R.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信素材直接文件
|
||||
* @param
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/materialOther")
|
||||
public ResponseEntity<byte[]> getMaterialOther(String appId, String mediaId, String fileName) throws Exception {
|
||||
try {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMaterialService wxMpMaterialService = wxMpService.getMaterialService();
|
||||
// 获取文件
|
||||
InputStream is = wxMpMaterialService.materialImageOrVoiceDownload(mediaId);
|
||||
byte[] body = new byte[is.available()];
|
||||
is.read(body);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
// 设置文件类型
|
||||
headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(fileName, "UTF-8"));
|
||||
headers.add("Content-Type", "application/octet-stream");
|
||||
HttpStatus statusCode = HttpStatus.OK;
|
||||
// 返回数据
|
||||
ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
|
||||
return entity;
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
e.printStackTrace();
|
||||
log.error("获取微信素材直接文件失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信临时素材直接文件
|
||||
* @param
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/tempMaterialOther")
|
||||
public ResponseEntity<byte[]> getTempMaterialOther(String appId, String mediaId, String fileName) throws Exception {
|
||||
try {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMaterialService wxMpMaterialService = wxMpService.getMaterialService();
|
||||
// 获取文件
|
||||
InputStream is = new FileInputStream(wxMpMaterialService.mediaDownload(mediaId));
|
||||
byte[] body = new byte[is.available()];
|
||||
is.read(body);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
// 设置文件类型
|
||||
headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(fileName, "UTF-8"));
|
||||
headers.add("Content-Type", "application/octet-stream");
|
||||
HttpStatus statusCode = HttpStatus.OK;
|
||||
// 返回数据
|
||||
ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
|
||||
return entity;
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
e.printStackTrace();
|
||||
log.error("获取微信素材直接文件失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.log.annotation.SysLog;
|
||||
import com.pig4cloud.pigx.mp.service.WxMenuService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 微信菜单管理
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:18
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/wx-menu")
|
||||
public class WxMenuController {
|
||||
|
||||
private final WxMenuService wxMenuService;
|
||||
|
||||
/**
|
||||
* 通过appid查询微信菜单
|
||||
* @param appId 公众号
|
||||
* @return R
|
||||
*/
|
||||
@GetMapping("/{appId}")
|
||||
public R getById(@PathVariable("appId") String appId) {
|
||||
return wxMenuService.getByAppId(appId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增微信菜单
|
||||
* @param wxMenu 微信菜单列表
|
||||
* @return R
|
||||
*/
|
||||
@SysLog("新增微信菜单")
|
||||
@PostMapping("/{appId}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmenu_add')")
|
||||
public R save(@RequestBody JSONObject wxMenu, @PathVariable String appId) {
|
||||
return R.ok(wxMenuService.save(wxMenu, appId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布微信菜单
|
||||
* @param appId 公众号
|
||||
* @return R
|
||||
*/
|
||||
@SysLog("发布微信菜单")
|
||||
@PutMapping("/{appId}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmenu_push')")
|
||||
public R updateById(@PathVariable String appId) {
|
||||
return wxMenuService.push(appId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{appId}")
|
||||
@PreAuthorize("@pms.hasPermission('mp_wxmenu_del')")
|
||||
public R delete(@PathVariable("appId") String appId) {
|
||||
return wxMenuService.delete(appId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.pig4cloud.pigx.mp.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantBroker;
|
||||
import com.pig4cloud.pigx.common.security.annotation.Inner;
|
||||
import com.pig4cloud.pigx.common.xss.core.XssCleanIgnore;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpContextHolder;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Binary Wang
|
||||
* @author lengleng
|
||||
* <p>
|
||||
* 微信接入入口
|
||||
*/
|
||||
@Slf4j
|
||||
@Inner(value = false)
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/{appId}/portal")
|
||||
public class WxPortalController {
|
||||
|
||||
/**
|
||||
* 微信接入校验处理
|
||||
* @param appId 多公众号标志位
|
||||
* @param signature 微信签名
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机数
|
||||
* @param echostr 随机字符串
|
||||
* @return
|
||||
*/
|
||||
@XssCleanIgnore
|
||||
@GetMapping(produces = "text/plain;charset=utf-8")
|
||||
public String authGet(@PathVariable("appId") String appId,
|
||||
@RequestParam(name = "signature", required = false) String signature,
|
||||
@RequestParam(name = "timestamp", required = false) String timestamp,
|
||||
@RequestParam(name = "nonce", required = false) String nonce,
|
||||
@RequestParam(name = "echostr", required = false) String echostr) {
|
||||
|
||||
log.info("接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, timestamp, nonce, echostr);
|
||||
|
||||
if (StrUtil.isAllBlank(signature, timestamp, nonce, echostr)) {
|
||||
throw new IllegalArgumentException("请求参数非法,请核实!");
|
||||
}
|
||||
|
||||
final WxMpService wxService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
|
||||
if (wxService == null) {
|
||||
throw new IllegalArgumentException(String.format("未找到对应appid=[%d]的配置,请核实!", appId));
|
||||
}
|
||||
|
||||
if (wxService.checkSignature(timestamp, nonce, signature)) {
|
||||
return echostr;
|
||||
}
|
||||
|
||||
return "非法请求";
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信消息处理
|
||||
* @param appId 多公众号标志位
|
||||
* @param requestBody 请求报文体
|
||||
* @param signature 微信签名
|
||||
* @param encType 加签方式
|
||||
* @param msgSignature 微信签名
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机数
|
||||
* @return
|
||||
*/
|
||||
@XssCleanIgnore
|
||||
@PostMapping(produces = "application/xml; charset=UTF-8")
|
||||
public String post(@PathVariable("appId") String appId, @RequestBody String requestBody,
|
||||
@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp,
|
||||
@RequestParam("nonce") String nonce, @RequestParam("openid") String openid,
|
||||
@RequestParam(name = "encrypt_type", required = false) String encType,
|
||||
@RequestParam(name = "msg_signature", required = false) String msgSignature) {
|
||||
return TenantBroker.applyAs(() -> WxMpInitConfigRunner.getTenants().get(appId),
|
||||
(id) -> handleMessage(appId, requestBody, signature, timestamp, nonce, openid, encType, msgSignature));
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信消息处理
|
||||
* @param appId 多公众号标志位
|
||||
* @param requestBody 请求报文体
|
||||
* @param signature 微信签名
|
||||
* @param encType 加签方式
|
||||
* @param msgSignature 微信签名
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机数
|
||||
* @return
|
||||
*/
|
||||
public String handleMessage(String appId, String requestBody, String signature, String timestamp, String nonce,
|
||||
String openid, String encType, String msgSignature) {
|
||||
|
||||
WxMpContextHolder.setAppId(appId);
|
||||
|
||||
final WxMpService wxService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
|
||||
log.info(
|
||||
"接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
|
||||
+ " timestamp=[{}], nonce=[{}], requestBody=[{}] ",
|
||||
openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
|
||||
|
||||
if (!wxService.checkSignature(timestamp, nonce, signature)) {
|
||||
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
|
||||
}
|
||||
|
||||
String out = null;
|
||||
|
||||
// 明文模式
|
||||
if (StrUtil.isBlank(encType)) {
|
||||
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
|
||||
WxMpXmlOutMessage outMessage = WxMpInitConfigRunner.getRouters().get(appId).route(inMessage);
|
||||
if (outMessage != null) {
|
||||
out = outMessage.toXml();
|
||||
}
|
||||
}
|
||||
|
||||
// aes加密模式
|
||||
if (SecurityConstants.AES.equalsIgnoreCase(encType)) {
|
||||
WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(),
|
||||
timestamp, nonce, msgSignature);
|
||||
|
||||
log.debug("消息解密后内容为:{} ", inMessage.toString());
|
||||
|
||||
WxMpXmlOutMessage outMessage = WxMpInitConfigRunner.getRouters().get(appId).route(inMessage);
|
||||
if (outMessage != null) {
|
||||
out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage());
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("组装回复信息:{}", out);
|
||||
WxMpContextHolder.clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.pig4cloud.pigx.common.core.sensitive.Sensitive;
|
||||
import com.pig4cloud.pigx.common.core.sensitive.SensitiveTypeEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 公众号账户
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:07:53
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxAccount extends Model<WxAccount> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 公众号名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 公众号账户
|
||||
*/
|
||||
private String account;
|
||||
|
||||
/**
|
||||
* 公众号appid
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 公众号密钥
|
||||
*/
|
||||
@Sensitive(type = SensitiveTypeEnum.KEY)
|
||||
private String appsecret;
|
||||
|
||||
/**
|
||||
* 公众号url
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 公众号token
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 加密密钥
|
||||
*/
|
||||
private String aeskey;
|
||||
|
||||
/**
|
||||
* 二维码图片URL
|
||||
*/
|
||||
private String qrUrl;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 -1:已删除 0:正常
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 租户
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 微信公众号粉丝
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:08:08
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxAccountFans extends Model<WxAccountFans> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户标识
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 订阅状态,0未关注,1已关注
|
||||
*/
|
||||
private String subscribeStatus;
|
||||
|
||||
/**
|
||||
* 订阅时间
|
||||
*/
|
||||
private LocalDateTime subscribeTime;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 性别,1男,2女,0未知
|
||||
*/
|
||||
private String gender;
|
||||
|
||||
/**
|
||||
* 语言
|
||||
*/
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 国家
|
||||
*/
|
||||
private String country;
|
||||
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 城市
|
||||
*/
|
||||
private String city;
|
||||
|
||||
/**
|
||||
* 头像地址
|
||||
*/
|
||||
private String headimgUrl;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 微信公众号ID
|
||||
*/
|
||||
private Long wxAccountId;
|
||||
|
||||
/**
|
||||
* 微信公众号appid
|
||||
*/
|
||||
private String wxAccountAppid;
|
||||
|
||||
/**
|
||||
* 微信公众号名
|
||||
*/
|
||||
private String wxAccountName;
|
||||
|
||||
/**
|
||||
* 标签ID列表
|
||||
*/
|
||||
private Long[] tagIds;
|
||||
|
||||
/**
|
||||
* 是否被拉黑
|
||||
*/
|
||||
private Integer isBlack;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 -1:已删除 0:正常
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.pig4cloud.pigx.mp.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 微信公众号账号标签
|
||||
*
|
||||
* @author lishangbu
|
||||
* @date 2021/12/31
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxAccountTag extends Model<WxAccount> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
@NotBlank(message = "标签不能为空")
|
||||
private String tag;
|
||||
|
||||
/**
|
||||
* 标签ID , 微信公众平台返回
|
||||
*/
|
||||
private Long tagId;
|
||||
|
||||
/**
|
||||
* 微信公众号ID
|
||||
*/
|
||||
private Long wxAccountId;
|
||||
|
||||
/**
|
||||
* 微信公众号appid
|
||||
*/
|
||||
private String wxAccountAppid;
|
||||
|
||||
/**
|
||||
* 微信公众号名
|
||||
*/
|
||||
private String wxAccountName;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 1:已删除 0:正常
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.pig4cloud.pigx.mp.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 消息自动回复
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-04-18 15:40:39
|
||||
*/
|
||||
@Data
|
||||
@TableName("wx_auto_reply")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxAutoReply extends Model<WxAutoReply> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 公众号ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 类型(1、关注时回复;2、消息回复;3、关键词回复)
|
||||
*/
|
||||
@NotNull(message = "类型不能为空")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 关键词
|
||||
*/
|
||||
private String reqKey;
|
||||
|
||||
/**
|
||||
* 请求消息类型(text:文本;image:图片;voice:语音;video:视频;shortvideo:小视频;location:地理位置)
|
||||
*/
|
||||
private String reqType;
|
||||
|
||||
/**
|
||||
* 回复消息类型(text:文本;image:图片;voice:语音;video:视频;music:音乐;news:图文)
|
||||
*/
|
||||
@NotNull(message = "回复消息类型不能为空")
|
||||
private String repType;
|
||||
|
||||
/**
|
||||
* 回复类型文本匹配类型(1、全匹配,2、半匹配)
|
||||
*/
|
||||
private String repMate;
|
||||
|
||||
/**
|
||||
* 回复类型文本保存文字
|
||||
*/
|
||||
private String repContent;
|
||||
|
||||
/**
|
||||
* 回复的素材名、视频和音乐的标题
|
||||
*/
|
||||
private String repName;
|
||||
|
||||
/**
|
||||
* 回复类型imge、voice、news、video的mediaID或音乐缩略图的媒体id
|
||||
*/
|
||||
private String repMediaId;
|
||||
|
||||
/**
|
||||
* 视频和音乐的描述
|
||||
*/
|
||||
private String repDesc;
|
||||
|
||||
/**
|
||||
* 链接
|
||||
*/
|
||||
private String repUrl;
|
||||
|
||||
/**
|
||||
* 高质量链接
|
||||
*/
|
||||
private String repHqUrl;
|
||||
|
||||
/**
|
||||
* 缩略图的媒体id
|
||||
*/
|
||||
private String repThumbMediaId;
|
||||
|
||||
/**
|
||||
* 缩略图url
|
||||
*/
|
||||
private String repThumbUrl;
|
||||
|
||||
/**
|
||||
* 图文消息的内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 -1:已删除 0:正常
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 微信粉丝消息回复表
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:48
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxFansMsgRes extends Model<WxFansMsgRes> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 粉丝消息ID
|
||||
*/
|
||||
private Long fansMsgId;
|
||||
|
||||
/**
|
||||
* 回复内容
|
||||
*/
|
||||
private String resContent;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 微信菜单表
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:18
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxMpMenu extends Model<WxMpMenu> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 菜单
|
||||
*/
|
||||
private String menu;
|
||||
|
||||
/**
|
||||
* 微信公众号ID
|
||||
*/
|
||||
private Long wxAccountId;
|
||||
|
||||
/**
|
||||
* 微信公众号appid
|
||||
*/
|
||||
private String wxAccountAppid;
|
||||
|
||||
/**
|
||||
* 微信公众号名
|
||||
*/
|
||||
private String wxAccountName;
|
||||
|
||||
/**
|
||||
* 是否发布 0 未发布 1 已发布
|
||||
*/
|
||||
private String pubFlag;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 -1:已删除 0:正常
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.pig4cloud.pigx.mp.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 微信消息
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-05-28 16:12:10
|
||||
*/
|
||||
@Data
|
||||
@TableName("wx_msg")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "微信消息")
|
||||
public class WxMsg extends Model<WxMsg> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/***
|
||||
* 公众号ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 公众号名称
|
||||
*/
|
||||
private String appName;
|
||||
|
||||
/**
|
||||
* 公众号logo
|
||||
*/
|
||||
private String appLogo;
|
||||
|
||||
/**
|
||||
* 微信用户ID
|
||||
*/
|
||||
private Long wxUserId;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String headimgUrl;
|
||||
|
||||
/**
|
||||
* 微信openId
|
||||
*/
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 消息分类(1、用户发给公众号;2、公众号发给用户;)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 消息类型(text:文本;image:图片;voice:语音;video:视频;shortvideo:小视频;location:地理位置;music:音乐;news:图文;event:推送事件)
|
||||
*/
|
||||
private String repType;
|
||||
|
||||
/**
|
||||
* 事件类型(subscribe:关注;unsubscribe:取关;CLICK、VIEW:菜单事件)
|
||||
*/
|
||||
private String repEvent;
|
||||
|
||||
/**
|
||||
* 回复类型文本保存文字
|
||||
*/
|
||||
private String repContent;
|
||||
|
||||
/**
|
||||
* 回复类型imge、voice、news、video的mediaID或音乐缩略图的媒体id
|
||||
*/
|
||||
private String repMediaId;
|
||||
|
||||
/**
|
||||
* 回复的素材名、视频和音乐的标题
|
||||
*/
|
||||
private String repName;
|
||||
|
||||
/**
|
||||
* 视频和音乐的描述
|
||||
*/
|
||||
private String repDesc;
|
||||
|
||||
/**
|
||||
* 链接
|
||||
*/
|
||||
private String repUrl;
|
||||
|
||||
/**
|
||||
* 高质量链接
|
||||
*/
|
||||
private String repHqUrl;
|
||||
|
||||
/**
|
||||
* 图文消息的内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 缩略图的媒体id
|
||||
*/
|
||||
private String repThumbMediaId;
|
||||
|
||||
/**
|
||||
* 缩略图url
|
||||
*/
|
||||
private String repThumbUrl;
|
||||
|
||||
/**
|
||||
* 地理位置维度
|
||||
*/
|
||||
private Double repLocationX;
|
||||
|
||||
/**
|
||||
* 地理位置经度
|
||||
*/
|
||||
private Double repLocationY;
|
||||
|
||||
/**
|
||||
* 地图缩放大小
|
||||
*/
|
||||
private Double repScale;
|
||||
|
||||
/**
|
||||
* 已读标记(0:是;1:否)
|
||||
*/
|
||||
private String readFlag;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 -1:已删除 0:正常
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.pig4cloud.pigx.mp.entity.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class WxAccountTagDeleteDTO {
|
||||
|
||||
private List<Long> ids;
|
||||
|
||||
private String wxAccountAppid;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.pig4cloud.pigx.mp.entity.vo;
|
||||
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountTag;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 粉丝Vo 对象
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/1/5
|
||||
*/
|
||||
@Data
|
||||
public class WxAccountFansVo extends WxAccountFans {
|
||||
|
||||
/**
|
||||
* 标签名称列表
|
||||
*/
|
||||
private List<WxAccountTag> tagList;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
public abstract class AbstractHandler implements WxMpMessageHandler {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Component
|
||||
public class KfSessionHandler extends AbstractHandler {
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService,
|
||||
WxSessionManager sessionManager) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpContextHolder;
|
||||
import com.pig4cloud.pigx.mp.constant.ReplyTypeEnum;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAutoReply;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxMsgMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxAutoReplyService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class LocationHandler extends AbstractHandler {
|
||||
|
||||
private final WxAutoReplyService wxAutoReplyService;
|
||||
|
||||
private final WxAccountFansMapper wxAccountFansMapper;
|
||||
|
||||
private final WxAccountMapper wxAccountMapper;
|
||||
|
||||
private final WxMsgMapper msgMapper;
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService,
|
||||
WxSessionManager sessionManager) {
|
||||
// 上报地理位置事件
|
||||
log.info("上报地理位置,纬度 : {},经度 : {},精度 : {}", wxMessage.getLatitude(), wxMessage.getLongitude(),
|
||||
wxMessage.getPrecision());
|
||||
|
||||
// 发送关注消息
|
||||
List<WxAutoReply> listWxAutoReply = wxAutoReplyService
|
||||
.list(Wrappers.<WxAutoReply>query().lambda().eq(WxAutoReply::getType, ReplyTypeEnum.MSG.getType())
|
||||
.eq(WxAutoReply::getAppId, WxMpContextHolder.getAppId())
|
||||
.eq(WxAutoReply::getReqType, wxMessage.getMsgType()));
|
||||
// 查询公众号 基本信息
|
||||
WxAccount wxAccount = wxAccountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAccount, wxMessage.getToUser()));
|
||||
|
||||
// 查询粉丝基本信息
|
||||
WxAccountFans fans = wxAccountFansMapper
|
||||
.selectOne(Wrappers.<WxAccountFans>lambdaQuery().eq(WxAccountFans::getOpenid, wxMessage.getFromUser()));
|
||||
|
||||
return MsgHandler.getWxMpXmlOutMessage(wxMessage, listWxAutoReply, fans, msgMapper, wxAccount);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pig4cloud.pigx.mp.constant.MsgTypeEnum;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMsg;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxMsgMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* <p>
|
||||
* 保存微信消息
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
|
||||
@AllArgsConstructor
|
||||
public class LogHandler extends AbstractHandler {
|
||||
|
||||
private final WxAccountFansMapper fansMapper;
|
||||
|
||||
private final WxAccountMapper accountMapper;
|
||||
|
||||
private final WxMsgMapper msgMapper;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService,
|
||||
WxSessionManager sessionManager) {
|
||||
log.debug("接收到请求消息,内容:{}", wxMessage.getContent());
|
||||
|
||||
WxAccount wxAccount = accountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAccount, wxMessage.getToUser()));
|
||||
|
||||
WxAccountFans fans = fansMapper
|
||||
.selectOne(Wrappers.<WxAccountFans>lambdaQuery().eq(WxAccountFans::getOpenid, wxMessage.getFromUser()));
|
||||
|
||||
if (fans == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WxMsg wxMsg = new WxMsg();
|
||||
wxMsg.setWxUserId(fans.getId());
|
||||
wxMsg.setNickName(fans.getNickname());
|
||||
wxMsg.setHeadimgUrl(fans.getHeadimgUrl());
|
||||
wxMsg.setType(MsgTypeEnum.USER2MP.getType());
|
||||
wxMsg.setRepEvent(wxMessage.getEvent());
|
||||
wxMsg.setRepType(wxMessage.getMsgType());
|
||||
wxMsg.setRepMediaId(wxMessage.getMediaId());
|
||||
wxMsg.setOpenId(fans.getOpenid());
|
||||
wxMsg.setAppId(wxAccount.getAppid());
|
||||
wxMsg.setAppLogo(wxAccount.getQrUrl());
|
||||
wxMsg.setAppName(wxAccount.getName());
|
||||
if (WxConsts.XmlMsgType.TEXT.equals(wxMessage.getMsgType())) {
|
||||
wxMsg.setRepContent(wxMessage.getContent());
|
||||
}
|
||||
|
||||
if (WxConsts.MenuButtonType.VIEW.equalsIgnoreCase(wxMessage.getEvent())) {
|
||||
wxMsg.setRepUrl(wxMessage.getEventKey());
|
||||
}
|
||||
|
||||
if (WxConsts.MenuButtonType.CLICK.equalsIgnoreCase(wxMessage.getEvent())) {
|
||||
wxMsg.setRepName(wxMessage.getEventKey());
|
||||
}
|
||||
|
||||
if (WxConsts.XmlMsgType.VOICE.equals(wxMessage.getMsgType())) {
|
||||
wxMsg.setRepName(wxMessage.getMediaId() + "." + wxMessage.getFormat());
|
||||
wxMsg.setRepContent(wxMessage.getRecognition());
|
||||
}
|
||||
if (WxConsts.XmlMsgType.IMAGE.equals(wxMessage.getMsgType())) {
|
||||
wxMsg.setRepUrl(wxMessage.getPicUrl());
|
||||
}
|
||||
if (WxConsts.XmlMsgType.LINK.equals(wxMessage.getMsgType())) {
|
||||
wxMsg.setRepName(wxMessage.getTitle());
|
||||
wxMsg.setRepDesc(wxMessage.getDescription());
|
||||
wxMsg.setRepUrl(wxMessage.getUrl());
|
||||
}
|
||||
if (WxConsts.MediaFileType.FILE.equals(wxMessage.getMsgType())) {
|
||||
wxMsg.setRepName(wxMessage.getTitle());
|
||||
wxMsg.setRepDesc(wxMessage.getDescription());
|
||||
}
|
||||
if (WxConsts.XmlMsgType.VIDEO.equals(wxMessage.getMsgType())) {
|
||||
wxMsg.setRepThumbMediaId(wxMessage.getThumbMediaId());
|
||||
}
|
||||
if (WxConsts.XmlMsgType.LOCATION.equals(wxMessage.getMsgType())) {
|
||||
wxMsg.setRepLocationX(wxMessage.getLocationX());
|
||||
wxMsg.setRepLocationY(wxMessage.getLocationY());
|
||||
wxMsg.setRepScale(wxMessage.getScale());
|
||||
wxMsg.setRepContent(wxMessage.getLabel());
|
||||
}
|
||||
msgMapper.insert(wxMsg);
|
||||
log.debug("保存微信用户信息成功 {}", wxMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpContextHolder;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMpMenu;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxMenuMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
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.message.WxMpXmlOutNewsMessage;
|
||||
import me.chanjar.weixin.mp.builder.outxml.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class MenuHandler extends AbstractHandler {
|
||||
|
||||
private final WxMenuMapper wxMenuMapper;
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService weixinService,
|
||||
WxSessionManager sessionManager) throws WxErrorException {
|
||||
// 组装菜单回复消息
|
||||
return getWxMpXmlOutMessage(wxMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装菜单回复消息
|
||||
* @param wxMessage
|
||||
* @return
|
||||
*/
|
||||
public WxMpXmlOutMessage getWxMpXmlOutMessage(WxMpXmlMessage wxMessage) {
|
||||
|
||||
WxMpMenu wxMpMenu = wxMenuMapper.selectOne(
|
||||
Wrappers.<WxMpMenu>lambdaQuery().eq(WxMpMenu::getWxAccountAppid, WxMpContextHolder.getAppId()));
|
||||
List<JSONObject> buttons = JSONUtil.parseObj(wxMpMenu.getMenu()).getJSONArray("button")
|
||||
.toList(JSONObject.class);
|
||||
String eventKey = wxMessage.getEventKey();
|
||||
|
||||
// 获取子集
|
||||
List<JSONObject> buttonList = new ArrayList<>();
|
||||
for (JSONObject button : buttons) {
|
||||
JSONArray subButton = button.getJSONArray("sub_button");
|
||||
if (subButton.isEmpty()) {
|
||||
buttonList.add(button);
|
||||
continue;
|
||||
}
|
||||
List<JSONObject> subButtonList = subButton.toList(JSONObject.class);
|
||||
buttonList.addAll(subButtonList);
|
||||
}
|
||||
|
||||
// 遍历规则
|
||||
for (JSONObject button : buttonList) {
|
||||
|
||||
// 可以在这里进行自定义逻辑开发
|
||||
if (!eventKey.equalsIgnoreCase(button.getStr("key"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WxMpXmlOutMessage wxMpXmlOutMessage = null;
|
||||
String repType = button.getStr("repType");
|
||||
String repName = button.getStr("repName");
|
||||
String repContent = button.getStr("repContent");
|
||||
String repMediaId = button.getStr("repMediaId");
|
||||
String repDesc = button.getStr("repDesc");
|
||||
if (WxConsts.KefuMsgType.TEXT.equals(repType)) {
|
||||
wxMpXmlOutMessage = new TextBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.content(repContent).build();
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.IMAGE.equals(repType)) {
|
||||
wxMpXmlOutMessage = new ImageBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.mediaId(repMediaId).build();
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.VOICE.equals(repType)) {
|
||||
wxMpXmlOutMessage = new VoiceBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.mediaId(repMediaId).build();
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.VIDEO.equals(repType)) {
|
||||
wxMpXmlOutMessage = new VideoBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.mediaId(repMediaId).title(repName).description(repDesc).build();
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.MUSIC.equals(repType)) {
|
||||
String repThumbMediaId = button.getStr("repThumbMediaId");
|
||||
String repUrl = button.getStr("repUrl");
|
||||
String repHqUrl = button.getStr("repHqUrl");
|
||||
|
||||
wxMpXmlOutMessage = new MusicBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.thumbMediaId(repThumbMediaId).title(repName).description(repDesc).musicUrl(repUrl)
|
||||
.hqMusicUrl(repHqUrl).build();
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.NEWS.equals(repType)) {
|
||||
String content = button.getStr("content");
|
||||
|
||||
List<WxMpXmlOutNewsMessage.Item> list = new ArrayList<>();
|
||||
List<JSONObject> articles = JSONUtil.parseObj(content).getJSONArray("articles")
|
||||
.toList(JSONObject.class);
|
||||
WxMpXmlOutNewsMessage.Item t;
|
||||
for (JSONObject jsonObject : articles) {
|
||||
t = new WxMpXmlOutNewsMessage.Item();
|
||||
t.setTitle(jsonObject.getStr("title"));
|
||||
t.setDescription(jsonObject.getStr("digest"));
|
||||
t.setPicUrl(jsonObject.getStr("thumbUrl"));
|
||||
t.setUrl(jsonObject.getStr("url"));
|
||||
list.add(t);
|
||||
}
|
||||
wxMpXmlOutMessage = new NewsBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.articles(list).build();
|
||||
}
|
||||
return wxMpXmlOutMessage;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpContextHolder;
|
||||
import com.pig4cloud.pigx.mp.constant.MsgTypeEnum;
|
||||
import com.pig4cloud.pigx.mp.constant.ReplyMateEnum;
|
||||
import com.pig4cloud.pigx.mp.constant.ReplyTypeEnum;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAutoReply;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMsg;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxMsgMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxAutoReplyService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
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.message.WxMpXmlOutNewsMessage;
|
||||
import me.chanjar.weixin.mp.builder.outxml.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
|
||||
|
||||
/**
|
||||
* @author JL
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class MsgHandler extends AbstractHandler {
|
||||
|
||||
private final WxAutoReplyService wxAutoReplyService;
|
||||
|
||||
private final WxAccountFansMapper wxAccountFansMapper;
|
||||
|
||||
private final WxAccountMapper wxAccountMapper;
|
||||
|
||||
private final WxMsgMapper wxMsgMapper;
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService,
|
||||
WxSessionManager sessionManager) {
|
||||
// 组装回复消息
|
||||
if (!wxMessage.getMsgType().equals(XmlMsgType.EVENT)) {
|
||||
WxMpXmlOutMessage rs;
|
||||
WxAccountFans fans = wxAccountFansMapper.selectOne(
|
||||
Wrappers.<WxAccountFans>lambdaQuery().eq(WxAccountFans::getOpenid, wxMessage.getFromUser()));
|
||||
// 查询公众号 基本信息
|
||||
WxAccount wxAccount = wxAccountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAccount, wxMessage.getToUser()));
|
||||
// 1、先处理是否有文本关键字回复
|
||||
if (WxConsts.KefuMsgType.TEXT.equals(wxMessage.getMsgType())) {
|
||||
// 先全匹配
|
||||
List<WxAutoReply> listWxAutoReply = wxAutoReplyService.list(
|
||||
Wrappers.<WxAutoReply>query().lambda().eq(WxAutoReply::getType, ReplyTypeEnum.KEYWORD.getType())
|
||||
.eq(WxAutoReply::getRepMate, ReplyMateEnum.ALL.getType())
|
||||
.eq(WxAutoReply::getAppId, WxMpContextHolder.getAppId())
|
||||
.eq(WxAutoReply::getReqKey, wxMessage.getContent()));
|
||||
if (listWxAutoReply != null && listWxAutoReply.size() > 0) {
|
||||
rs = getWxMpXmlOutMessage(wxMessage, listWxAutoReply, fans, wxMsgMapper, wxAccount);
|
||||
if (rs != null) {
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
// 再半匹配
|
||||
listWxAutoReply = wxAutoReplyService.list(
|
||||
Wrappers.<WxAutoReply>query().lambda().eq(WxAutoReply::getType, ReplyTypeEnum.KEYWORD.getType())
|
||||
.eq(WxAutoReply::getAppId, WxMpContextHolder.getAppId())
|
||||
.eq(WxAutoReply::getRepMate, ReplyMateEnum.LIKE.getType())
|
||||
.like(WxAutoReply::getReqKey, wxMessage.getContent()));
|
||||
if (listWxAutoReply != null && listWxAutoReply.size() > 0) {
|
||||
rs = getWxMpXmlOutMessage(wxMessage, listWxAutoReply, fans, wxMsgMapper, wxAccount);
|
||||
if (rs != null) {
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2、再处理消息回复
|
||||
List<WxAutoReply> listWxAutoReply = wxAutoReplyService
|
||||
.list(Wrappers.<WxAutoReply>query().lambda().eq(WxAutoReply::getAppId, WxMpContextHolder.getAppId())
|
||||
.eq(WxAutoReply::getType, ReplyTypeEnum.MSG.getType())
|
||||
.eq(WxAutoReply::getReqType, wxMessage.getMsgType()));
|
||||
rs = getWxMpXmlOutMessage(wxMessage, listWxAutoReply, fans, wxMsgMapper, wxAccount);
|
||||
return rs;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装回复消息,并记录消息
|
||||
* @param wxMessage
|
||||
* @param listWxAutoReply
|
||||
* @param wxMsgMapper
|
||||
* @param wxAccount
|
||||
* @return
|
||||
*/
|
||||
public static WxMpXmlOutMessage getWxMpXmlOutMessage(WxMpXmlMessage wxMessage, List<WxAutoReply> listWxAutoReply,
|
||||
WxAccountFans fans, WxMsgMapper wxMsgMapper, WxAccount wxAccount) {
|
||||
WxMpXmlOutMessage wxMpXmlOutMessage = null;
|
||||
// 记录接收消息
|
||||
|
||||
if (listWxAutoReply != null && listWxAutoReply.size() > 0) {
|
||||
WxAutoReply wxAutoReply = listWxAutoReply.get(0);
|
||||
// 记录回复消息
|
||||
WxMsg wxMsg = new WxMsg();
|
||||
wxMsg.setWxUserId(fans.getId());
|
||||
wxMsg.setNickName(fans.getNickname());
|
||||
wxMsg.setHeadimgUrl(fans.getHeadimgUrl());
|
||||
wxMsg.setType(MsgTypeEnum.MP2USER.getType());
|
||||
wxMsg.setRepType(wxAutoReply.getRepType());
|
||||
wxMsg.setOpenId(fans.getOpenid());
|
||||
wxMsg.setAppName(wxAccount.getName());
|
||||
wxMsg.setAppLogo(wxAccount.getQrUrl());
|
||||
wxMsg.setAppId(wxAccount.getAppid());
|
||||
// 文本
|
||||
if (WxConsts.KefuMsgType.TEXT.equals(wxAutoReply.getRepType())) {
|
||||
wxMsg.setRepContent(wxAutoReply.getRepContent());
|
||||
wxMpXmlOutMessage = new TextBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.content(wxAutoReply.getRepContent()).build();
|
||||
}
|
||||
|
||||
// 图片
|
||||
if (WxConsts.KefuMsgType.IMAGE.equals(wxAutoReply.getRepType())) {
|
||||
wxMsg.setRepName(wxAutoReply.getRepName());
|
||||
wxMsg.setRepUrl(wxAutoReply.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxAutoReply.getRepMediaId());
|
||||
wxMpXmlOutMessage = new ImageBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.mediaId(wxAutoReply.getRepMediaId()).build();
|
||||
}
|
||||
if (WxConsts.KefuMsgType.VOICE.equals(wxAutoReply.getRepType())) {
|
||||
wxMsg.setRepName(wxAutoReply.getRepName());
|
||||
wxMsg.setRepUrl(wxAutoReply.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxAutoReply.getRepMediaId());
|
||||
wxMpXmlOutMessage = new VoiceBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.mediaId(wxAutoReply.getRepMediaId()).build();
|
||||
}
|
||||
if (WxConsts.KefuMsgType.VIDEO.equals(wxAutoReply.getRepType())) {
|
||||
wxMsg.setRepName(wxAutoReply.getRepName());
|
||||
wxMsg.setRepDesc(wxAutoReply.getRepDesc());
|
||||
wxMsg.setRepUrl(wxAutoReply.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxAutoReply.getRepMediaId());
|
||||
wxMpXmlOutMessage = new VideoBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.mediaId(wxAutoReply.getRepMediaId()).title(wxAutoReply.getRepName())
|
||||
.description(wxAutoReply.getRepDesc()).build();
|
||||
}
|
||||
if (WxConsts.KefuMsgType.MUSIC.equals(wxAutoReply.getRepType())) {
|
||||
wxMsg.setRepName(wxAutoReply.getRepName());
|
||||
wxMsg.setRepDesc(wxAutoReply.getRepDesc());
|
||||
wxMsg.setRepUrl(wxAutoReply.getRepUrl());
|
||||
wxMsg.setRepHqUrl(wxAutoReply.getRepHqUrl());
|
||||
wxMsg.setRepThumbMediaId(wxAutoReply.getRepThumbMediaId());
|
||||
wxMsg.setRepThumbUrl(wxAutoReply.getRepThumbUrl());
|
||||
wxMpXmlOutMessage = new MusicBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.thumbMediaId(wxAutoReply.getRepThumbMediaId()).title(wxAutoReply.getRepName())
|
||||
.description(wxAutoReply.getRepDesc()).musicUrl(wxAutoReply.getRepUrl())
|
||||
.hqMusicUrl(wxAutoReply.getRepHqUrl()).build();
|
||||
}
|
||||
if (WxConsts.KefuMsgType.NEWS.equals(wxAutoReply.getRepType())) {
|
||||
List<WxMpXmlOutNewsMessage.Item> list = new ArrayList<>();
|
||||
JSONArray jsonArray = JSONUtil.parseArray(wxAutoReply.getContent());
|
||||
for (Object obj : jsonArray) {
|
||||
JSONObject jsonObject = (JSONObject) obj;
|
||||
WxMpXmlOutNewsMessage.Item t = new WxMpXmlOutNewsMessage.Item();
|
||||
t.setTitle(jsonObject.getStr("title"));
|
||||
t.setDescription(jsonObject.getStr("digest"));
|
||||
t.setPicUrl(jsonObject.getStr("thumbUrl"));
|
||||
t.setUrl(jsonObject.getStr("url"));
|
||||
list.add(t);
|
||||
}
|
||||
wxMsg.setRepName(wxAutoReply.getRepName());
|
||||
wxMsg.setRepDesc(wxAutoReply.getRepDesc());
|
||||
wxMsg.setRepUrl(wxAutoReply.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxAutoReply.getRepMediaId());
|
||||
wxMsg.setContent(wxAutoReply.getContent());
|
||||
wxMpXmlOutMessage = new NewsBuilder().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
|
||||
.articles(list).build();
|
||||
}
|
||||
wxMsgMapper.insert(wxMsg);
|
||||
}
|
||||
return wxMpXmlOutMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Component
|
||||
public class NullHandler extends AbstractHandler {
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService,
|
||||
WxSessionManager sessionManager) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Component
|
||||
public class ScanHandler extends AbstractHandler {
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> map, WxMpService wxMpService,
|
||||
WxSessionManager wxSessionManager) throws WxErrorException {
|
||||
// 扫码事件处理
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 门店审核事件处理
|
||||
*
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Component
|
||||
public class StoreCheckNotifyHandler extends AbstractHandler {
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService,
|
||||
WxSessionManager sessionManager) {
|
||||
// TODO 处理门店审核事件
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
import cn.binarywang.tools.generator.ChineseNameGenerator;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pig4cloud.pigx.mp.builder.TextBuilder;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpContextHolder;
|
||||
import com.pig4cloud.pigx.mp.constant.ReplyTypeEnum;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAutoReply;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxMsgMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxAutoReplyService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class SubscribeHandler extends AbstractHandler {
|
||||
|
||||
private final WxAutoReplyService wxAutoReplyService;
|
||||
|
||||
private final WxAccountFansMapper wxAccountFansMapper;
|
||||
|
||||
private final WxAccountMapper wxAccountMapper;
|
||||
|
||||
private final WxMsgMapper msgMapper;
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService weixinService,
|
||||
WxSessionManager sessionManager) throws WxErrorException {
|
||||
|
||||
log.info("新关注用户 OPENID: " + wxMessage.getFromUser());
|
||||
|
||||
// 获取微信用户基本信息
|
||||
try {
|
||||
WxMpUser wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser(), null);
|
||||
WxAccountFans wxAccountFans = wxAccountFansMapper.selectOne(
|
||||
Wrappers.<WxAccountFans>lambdaUpdate().eq(WxAccountFans::getOpenid, wxMessage.getFromUser()));
|
||||
|
||||
// 删除历史关注保存的信息
|
||||
if (wxAccountFans != null) {
|
||||
wxAccountFansMapper.deleteById(wxAccountFans);
|
||||
}
|
||||
|
||||
wxAccountFans = new WxAccountFans();
|
||||
wxAccountFans.setOpenid(wxMpUser.getOpenId());
|
||||
wxAccountFans.setSubscribeStatus(String.valueOf(BooleanUtil.toInt(wxMpUser.getSubscribe())));
|
||||
wxAccountFans.setSubscribeTime(LocalDateTime
|
||||
.ofInstant(Instant.ofEpochMilli(wxMpUser.getSubscribeTime() * 1000L), ZoneId.systemDefault()));
|
||||
|
||||
// 随机生成一个昵称,方便平台内部使用
|
||||
String generatedName = ChineseNameGenerator.getInstance().generate();
|
||||
wxAccountFans.setNickname(generatedName);
|
||||
wxAccountFans.setLanguage(wxMpUser.getLanguage());
|
||||
wxAccountFans.setHeadimgUrl(wxMpUser.getHeadImgUrl());
|
||||
wxAccountFans.setRemark(wxMpUser.getRemark());
|
||||
wxAccountFans.setTagIds(wxAccountFans.getTagIds());
|
||||
|
||||
WxAccount wxAccount = wxAccountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAppid, WxMpContextHolder.getAppId()));
|
||||
wxAccountFans.setWxAccountId(wxAccount.getId());
|
||||
wxAccountFans.setWxAccountAppid(wxAccount.getAppid());
|
||||
wxAccountFans.setWxAccountName(wxAccount.getName());
|
||||
|
||||
wxAccountFansMapper.insert(wxAccountFans);
|
||||
return this.handleSpecial(wxMessage, wxAccountFans);
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error("该公众号没有获取用户信息失败!", e);
|
||||
}
|
||||
|
||||
return new TextBuilder().build("感谢关注", wxMessage, weixinService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理特殊请求,比如如果是扫码进来的,可以做相应处理
|
||||
*/
|
||||
private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage, WxAccountFans fans) {
|
||||
// 发送关注消息
|
||||
List<WxAutoReply> listWxAutoReply = wxAutoReplyService
|
||||
.list(Wrappers.<WxAutoReply>query().lambda().eq(WxAutoReply::getType, ReplyTypeEnum.ATTENTION.getType())
|
||||
.eq(WxAutoReply::getAppId, WxMpContextHolder.getAppId()));
|
||||
// 查询公众号 基本信息
|
||||
WxAccount wxAccount = wxAccountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAccount, wxMessage.getToUser()));
|
||||
WxMpXmlOutMessage wxMpXmlOutMessage = MsgHandler.getWxMpXmlOutMessage(wxMessage, listWxAutoReply, fans,
|
||||
msgMapper, wxAccount);
|
||||
return wxMpXmlOutMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.pig4cloud.pigx.mp.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pig4cloud.pigx.mp.constant.SubStatusEnum;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Binary Wang(https://github.com/binarywang)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class UnsubscribeHandler extends AbstractHandler {
|
||||
|
||||
private final WxAccountFansMapper wxAccountFansMapper;
|
||||
|
||||
@Override
|
||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService,
|
||||
WxSessionManager sessionManager) {
|
||||
String openId = wxMessage.getFromUser();
|
||||
log.info("取消关注用户 OPENID: " + openId);
|
||||
WxAccountFans fans = new WxAccountFans();
|
||||
fans.setSubscribeStatus(SubStatusEnum.UNSUB.getType());
|
||||
wxAccountFansMapper.update(fans, Wrappers.<WxAccountFans>lambdaUpdate().eq(WxAccountFans::getOpenid, openId));
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.mapper;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 微信公众号粉丝
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:08:08
|
||||
*/
|
||||
@Mapper
|
||||
public interface WxAccountFansMapper extends PigxBaseMapper<WxAccountFans> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.mapper;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 公众号账户
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:07:53
|
||||
*/
|
||||
@Mapper
|
||||
public interface WxAccountMapper extends PigxBaseMapper<WxAccount> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.mapper;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountTag;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 公众号账户
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:07:53
|
||||
*/
|
||||
@Mapper
|
||||
public interface WxAccountTagMapper extends PigxBaseMapper<WxAccountTag> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.pig4cloud.pigx.mp.mapper;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAutoReply;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 消息自动回复
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-04-18 15:40:39
|
||||
*/
|
||||
@Mapper
|
||||
public interface WxAutoReplyMapper extends PigxBaseMapper<WxAutoReply> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.mapper;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
|
||||
import com.pig4cloud.pigx.mp.entity.WxFansMsgRes;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* lengleng
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:48
|
||||
*/
|
||||
@Mapper
|
||||
public interface WxFansMsgResMapper extends PigxBaseMapper<WxFansMsgRes> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.mapper;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMpMenu;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* lengleng
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:18
|
||||
*/
|
||||
@Mapper
|
||||
public interface WxMenuMapper extends PigxBaseMapper<WxMpMenu> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.pig4cloud.pigx.mp.mapper;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMsg;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 微信消息
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-05-28 16:12:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface WxMsgMapper extends PigxBaseMapper<WxMsg> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
|
||||
/**
|
||||
* 微信公众号粉丝
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:08:08
|
||||
*/
|
||||
public interface WxAccountFansService extends IService<WxAccountFans> {
|
||||
|
||||
/**
|
||||
* 同步指定公众号粉丝
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
Boolean syncAccountFans(String appId);
|
||||
|
||||
/**
|
||||
* 分页查询粉丝
|
||||
* @param page 粉丝
|
||||
* @param wxAccountFans 查询条件
|
||||
* @return
|
||||
*/
|
||||
IPage getFansWithTagPage(Page page, WxAccountFans wxAccountFans);
|
||||
|
||||
/**
|
||||
* 更新粉丝信息
|
||||
* @param wxAccountFans 信息
|
||||
* @return
|
||||
*/
|
||||
Boolean updateFans(WxAccountFans wxAccountFans);
|
||||
|
||||
Boolean unblack(Long[] ids, String appId);
|
||||
|
||||
Boolean black(Long[] ids, String appId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
|
||||
/**
|
||||
* 公众号账户
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:07:53
|
||||
*/
|
||||
public interface WxAccountService extends IService<WxAccount> {
|
||||
|
||||
/**
|
||||
* 生成公众号二维码
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
R generateQr(String appId);
|
||||
|
||||
/**
|
||||
* 获取公众号统计数据
|
||||
* @param appId
|
||||
* @param interval
|
||||
* @return
|
||||
*/
|
||||
R statistics(String appId, String interval);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.pig4cloud.pigx.mp.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountTag;
|
||||
|
||||
/**
|
||||
* 微信账户标签服务
|
||||
*
|
||||
* @author lishangbu
|
||||
* @date 2021/12/31
|
||||
*/
|
||||
public interface WxAccountTagService extends IService<WxAccountTag> {
|
||||
|
||||
/**
|
||||
* 保存账户标签
|
||||
* @param accountTag 账户标签
|
||||
* @return 保存成功后的账户标签
|
||||
*/
|
||||
WxAccountTag saveAccountTag(WxAccountTag accountTag);
|
||||
|
||||
/**
|
||||
* 更新账户标签
|
||||
* @param accountTag 待更新的账户标签
|
||||
* @return 修改成功后的账户标签
|
||||
*/
|
||||
WxAccountTag updateAccountTag(WxAccountTag accountTag);
|
||||
|
||||
/**
|
||||
* 删除账户标签
|
||||
* @param wxAccountTag 待删除的账户标签
|
||||
* @return 删除成功返回true,删除失败返回false
|
||||
*/
|
||||
Boolean removeAccountTagById(WxAccountTag wxAccountTag);
|
||||
|
||||
/**
|
||||
* 同步账户标签
|
||||
* @param appId appId
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean syncAccountTags(String appId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.pig4cloud.pigx.mp.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAutoReply;
|
||||
|
||||
/**
|
||||
* 消息自动回复
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-04-18 15:40:39
|
||||
*/
|
||||
public interface WxAutoReplyService extends IService<WxAutoReply> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMpMenu;
|
||||
|
||||
/**
|
||||
* 微信菜单业务
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:18
|
||||
*/
|
||||
public interface WxMenuService extends IService<WxMpMenu> {
|
||||
|
||||
/**
|
||||
* 新增微信公众号按钮
|
||||
* @param wxMenus json
|
||||
* @param appId 公众号信息
|
||||
* @return
|
||||
*/
|
||||
Boolean save(JSONObject wxMenus, String appId);
|
||||
|
||||
/**
|
||||
* 发布到微信
|
||||
* @param appId 公众号信息
|
||||
* @return
|
||||
*/
|
||||
R push(String appId);
|
||||
|
||||
/**
|
||||
* 通过appid 查询菜单信息
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
R getByAppId(String appId);
|
||||
|
||||
/**
|
||||
* 通过appid 删除菜单
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
R delete(String appId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.pig4cloud.pigx.mp.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMsg;
|
||||
|
||||
/**
|
||||
* 微信消息
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-05-28 16:12:10
|
||||
*/
|
||||
public interface WxMsgService extends IService<WxMsg> {
|
||||
|
||||
/**
|
||||
* 保存信息并向用户推送
|
||||
* @param wxMsg
|
||||
* @return
|
||||
*/
|
||||
R saveAndPushMsg(WxMsg wxMsg);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.service.impl;
|
||||
|
||||
import cn.binarywang.tools.generator.ChineseNameGenerator;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantBroker;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountTag;
|
||||
import com.pig4cloud.pigx.mp.entity.vo.WxAccountFansVo;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountTagMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxAccountFansService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.api.WxMpUserBlacklistService;
|
||||
import me.chanjar.weixin.mp.api.WxMpUserService;
|
||||
import me.chanjar.weixin.mp.api.WxMpUserTagService;
|
||||
import me.chanjar.weixin.mp.bean.result.WxMpUser;
|
||||
import me.chanjar.weixin.mp.bean.result.WxMpUserList;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:08:08
|
||||
* <p>
|
||||
* 微信公众号粉丝
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class WxAccountFansServiceImpl extends ServiceImpl<WxAccountFansMapper, WxAccountFans>
|
||||
implements WxAccountFansService {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
|
||||
private final WxAccountTagMapper wxAccountTagMapper;
|
||||
|
||||
private final WxAccountMapper wxAccountMapper;
|
||||
|
||||
/**
|
||||
* 分页查询粉丝
|
||||
* @param page 粉丝
|
||||
* @param wxAccountFans 查询条件
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public IPage getFansWithTagPage(Page page, WxAccountFans wxAccountFans) {
|
||||
// 查询当前公众号的标签
|
||||
List<WxAccountTag> wxAccountTags = wxAccountTagMapper.selectList(Wrappers.emptyWrapper());
|
||||
|
||||
// 翻页查询粉丝
|
||||
LambdaQueryWrapper<WxAccountFans> wrapper = Wrappers.<WxAccountFans>lambdaQuery()
|
||||
.eq(StrUtil.isNotBlank(wxAccountFans.getWxAccountAppid()), WxAccountFans::getWxAccountAppid,
|
||||
wxAccountFans.getWxAccountAppid())
|
||||
.like(StrUtil.isNotBlank(wxAccountFans.getNickname()), WxAccountFans::getNickname,
|
||||
wxAccountFans.getNickname());
|
||||
|
||||
IPage<WxAccountFans> fansPage = baseMapper.selectPage(page, wrapper);
|
||||
|
||||
List<WxAccountFansVo> voList = fansPage.getRecords().stream().map(fans -> {//
|
||||
WxAccountFansVo vo = new WxAccountFansVo();
|
||||
BeanUtils.copyProperties(fans, vo);
|
||||
// 赋值 TAG_NAME
|
||||
if (ArrayUtil.isNotEmpty(fans.getTagIds())) {
|
||||
List<WxAccountTag> tagList = wxAccountTags.stream()
|
||||
.filter(tag -> tag.getWxAccountAppid().endsWith(fans.getWxAccountAppid()) //
|
||||
&& ArrayUtil.contains(fans.getTagIds(), tag.getTagId()))
|
||||
.collect(Collectors.toList());
|
||||
vo.setTagList(tagList);
|
||||
}
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
page.setRecords(voList);
|
||||
page.setTotal(fansPage.getTotal());
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新粉丝信息
|
||||
* @param wxAccountFans 信息
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateFans(WxAccountFans wxAccountFans) {
|
||||
baseMapper.updateById(wxAccountFans);
|
||||
|
||||
// 获取操作微信接口类
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(wxAccountFans.getWxAccountAppid());
|
||||
|
||||
if (StrUtil.isNotBlank(wxAccountFans.getRemark())) {
|
||||
WxMpUserService wxMpUserService = wxMpService.getUserService();
|
||||
wxMpUserService.userUpdateRemark(wxAccountFans.getOpenid(), wxAccountFans.getRemark());
|
||||
|
||||
}
|
||||
|
||||
// 更新用户标签
|
||||
WxMpUserTagService userTagService = wxMpService.getUserTagService();
|
||||
if (ArrayUtil.isNotEmpty(wxAccountFans.getTagIds())) {
|
||||
// 移除原有标签
|
||||
List<Long> oldTag = userTagService.userTagList(wxAccountFans.getOpenid());
|
||||
for (Long tagId : oldTag) {
|
||||
userTagService.batchUntagging(tagId, new String[] { wxAccountFans.getOpenid() });
|
||||
}
|
||||
|
||||
// 添加新标签
|
||||
for (Long tagId : wxAccountFans.getTagIds()) {
|
||||
|
||||
userTagService.batchTagging(tagId, new String[] { wxAccountFans.getOpenid() });
|
||||
}
|
||||
}
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Boolean unblack(Long[] ids, String appId) {
|
||||
List<WxAccountFans> wxAccountFans = this.listByIds(CollUtil.toList(ids));
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpUserBlacklistService blackListService = wxMpService.getBlackListService();
|
||||
List<String> collect = wxAccountFans.stream().map(WxAccountFans::getOpenid).collect(Collectors.toList());
|
||||
blackListService.pullFromBlacklist(collect);
|
||||
// 更新数据
|
||||
List<WxAccountFans> fansList = wxAccountFans.stream().peek(item -> {
|
||||
item.setIsBlack(CommonConstants.SUCCESS);
|
||||
}).collect(Collectors.toList());
|
||||
this.updateBatchById(fansList);
|
||||
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Boolean black(Long[] ids, String appId) {
|
||||
List<WxAccountFans> wxAccountFans = this.listByIds(CollUtil.toList(ids));
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpUserBlacklistService blackListService = wxMpService.getBlackListService();
|
||||
List<String> collect = wxAccountFans.stream().map(WxAccountFans::getOpenid).collect(Collectors.toList());
|
||||
blackListService.pushToBlacklist(collect);
|
||||
// 更新数据
|
||||
List<WxAccountFans> fansList = wxAccountFans.stream().peek(item -> {
|
||||
item.setIsBlack(CommonConstants.FAIL);
|
||||
}).collect(Collectors.toList());
|
||||
this.updateBatchById(fansList);
|
||||
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公众号粉丝,生产建议异步
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@Async
|
||||
@Override
|
||||
public Boolean syncAccountFans(String appId) {
|
||||
WxAccount wxAccount = wxAccountMapper
|
||||
.selectOne(Wrappers.<WxAccount>query().lambda().eq(WxAccount::getAppid, appId));
|
||||
|
||||
// 获取操作微信接口类
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpUserService wxMpUserService = wxMpService.getUserService();
|
||||
// 根据公众号查询已同步用户openid 查询最新的一条 (注意不能按订阅时间排序)
|
||||
String finalNextOpenId = queryNextOpenId(appId);
|
||||
TenantBroker.runAs(() -> WxMpInitConfigRunner.getTenants().get(appId),
|
||||
(id) -> fetchUser(finalNextOpenId, wxAccount, wxMpUserService));
|
||||
|
||||
log.info("公众号 {} 粉丝同步完成", wxAccount.getName());
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信用户
|
||||
* @param nextOpenid 下一组开始的openid
|
||||
* @param wxAccount 公众号信息
|
||||
* @param wxMpUserService mp操作类
|
||||
*/
|
||||
private void fetchUser(String nextOpenid, WxAccount wxAccount, WxMpUserService wxMpUserService) {
|
||||
try {
|
||||
WxMpUserList wxMpUserList = wxMpUserService.userList(nextOpenid);
|
||||
// openId 分组 每组 100个 openid
|
||||
List<List<String>> openIdsList = CollUtil.split(wxMpUserList.getOpenids(), SIZE).stream()
|
||||
.filter(CollUtil::isNotEmpty).collect(Collectors.toList());
|
||||
// 处理每个分组. 调用查询用户信息
|
||||
for (List<String> openIdList : openIdsList) {
|
||||
log.info("开始批量获取用户信息 {}", openIdList);
|
||||
List<WxAccountFans> wxAccountFansList = new ArrayList<>();
|
||||
wxMpUserService.userInfoList(openIdList).forEach(wxMpUser -> {
|
||||
WxAccountFans wxAccountFans = buildDbUser(wxAccount, wxMpUser);
|
||||
wxAccountFansList.add(wxAccountFans);
|
||||
});
|
||||
this.saveOrUpdateBatch(wxAccountFansList);
|
||||
log.info("批量插入用户信息完成 {}", openIdList);
|
||||
}
|
||||
// 如果nextOpenId 不为空,则继续获取
|
||||
if (StrUtil.isNotBlank(wxMpUserList.getNextOpenid())) {
|
||||
fetchUser(wxMpUserList.getNextOpenid(), wxAccount, wxMpUserService);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("同步微信公众号 {} 粉丝异常 {}", wxAccount.getName(), e.getMessage());
|
||||
// 失败重试逻辑
|
||||
String finalNextOpenId = queryNextOpenId(wxAccount.getAppid());
|
||||
fetchUser(finalNextOpenId, wxAccount, wxMpUserService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建数据库存取对象
|
||||
* @param wxAccount 公众号信息
|
||||
* @param wxMpUser 用户微信信息
|
||||
*/
|
||||
private WxAccountFans buildDbUser(WxAccount wxAccount, WxMpUser wxMpUser) {
|
||||
WxAccountFans wxAccountFans = new WxAccountFans();
|
||||
wxAccountFans.setOpenid(wxMpUser.getOpenId());
|
||||
wxAccountFans.setSubscribeStatus(String.valueOf(BooleanUtil.toInt(wxMpUser.getSubscribe())));
|
||||
|
||||
// 2020-11-25 部分用户订阅时间为空,跳过此字段处理
|
||||
if (ObjectUtil.isNotEmpty(wxMpUser.getSubscribeTime())) {
|
||||
wxAccountFans.setSubscribeTime(LocalDateTime
|
||||
.ofInstant(Instant.ofEpochMilli(wxMpUser.getSubscribeTime() * 1000L), ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
// 随机生成一个昵称,方便平台内部使用
|
||||
String generatedName = ChineseNameGenerator.getInstance().generate();
|
||||
wxAccountFans.setNickname(generatedName);
|
||||
wxAccountFans.setLanguage(wxMpUser.getLanguage());
|
||||
wxAccountFans.setHeadimgUrl(wxMpUser.getHeadImgUrl());
|
||||
wxAccountFans.setRemark(wxMpUser.getRemark());
|
||||
wxAccountFans.setTagIds(wxAccountFans.getTagIds());
|
||||
wxAccountFans.setWxAccountId(wxAccount.getId());
|
||||
wxAccountFans.setWxAccountAppid(wxAccount.getAppid());
|
||||
wxAccountFans.setWxAccountName(wxAccount.getName());
|
||||
wxAccountFans.setIsBlack(CommonConstants.SUCCESS);
|
||||
return wxAccountFans;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前公众号最后插入的openId
|
||||
* @param appId 公众号标识
|
||||
* @return openid / null
|
||||
*/
|
||||
private String queryNextOpenId(String appId) {
|
||||
Page<WxAccountFans> queryPage = new Page<>(0, 1);
|
||||
Page<WxAccountFans> fansPage = baseMapper.selectPage(queryPage, Wrappers.<WxAccountFans>query().lambda()
|
||||
.eq(WxAccountFans::getWxAccountAppid, appId).orderByDesc(WxAccountFans::getCreateTime));
|
||||
String nextOpenId = null;
|
||||
if (fansPage.getTotal() > 0) {
|
||||
nextOpenId = fansPage.getRecords().get(0).getOpenid();
|
||||
}
|
||||
return nextOpenId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxAccountService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.mp.api.WxMpDataCubeService;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeArticleResult;
|
||||
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult;
|
||||
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult;
|
||||
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate;
|
||||
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 公众号账户
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-26 22:07:53
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxAccountServiceImpl extends ServiceImpl<WxAccountMapper, WxAccount> implements WxAccountService {
|
||||
|
||||
/**
|
||||
* 生成公众号二维码
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
|
||||
@Override
|
||||
public R generateQr(String appId) {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
try {
|
||||
WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateLastTicket(1);
|
||||
String url = wxMpService.getQrcodeService().qrCodePictureUrl(ticket.getTicket());
|
||||
|
||||
WxAccount wxAccount = baseMapper
|
||||
.selectOne(Wrappers.<WxAccount>query().lambda().eq(WxAccount::getAppid, appId));
|
||||
wxAccount.setQrUrl(url);
|
||||
baseMapper.updateById(wxAccount);
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error(" 获取公众号二维码失败", e);
|
||||
return R.failed("更新公众号二维码失败");
|
||||
}
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公众号统计数据
|
||||
* @param appId 公众号信息
|
||||
* @param interval 时间间隔
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public R statistics(String appId, String interval) {
|
||||
String[] split = interval.split(StrUtil.DASHED);
|
||||
Date start = new Date(Long.parseLong(split[0]));
|
||||
Date end = new Date(Long.parseLong(split[1]));
|
||||
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpDataCubeService cubeService = wxMpService.getDataCubeService();
|
||||
|
||||
List<List<Object>> result = new ArrayList<>();
|
||||
try {
|
||||
// 获取累计用户数据
|
||||
List<Object> cumulateList = cubeService.getUserCumulate(start, end).stream()
|
||||
.map(WxDataCubeUserCumulate::getCumulateUser).collect(Collectors.toList());
|
||||
result.add(cumulateList);
|
||||
|
||||
// 获取用户分享数据
|
||||
List<Object> shareList = cubeService.getUserShare(start, end).stream()
|
||||
.map(WxDataCubeArticleResult::getShareCount).collect(Collectors.toList());
|
||||
result.add(shareList);
|
||||
|
||||
// 获取消息发送概况数据
|
||||
List<Object> upstreamList = cubeService.getUpstreamMsg(start, end).stream()
|
||||
.map(WxDataCubeMsgResult::getMsgCount).collect(Collectors.toList());
|
||||
result.add(upstreamList);
|
||||
|
||||
// 获取接口调用概况数据
|
||||
List<WxDataCubeInterfaceResult> interfaceSummaryList = cubeService.getInterfaceSummary(start, end);
|
||||
List<Object> interfaceList = interfaceSummaryList.stream().map(WxDataCubeInterfaceResult::getCallbackCount)
|
||||
.collect(Collectors.toList());
|
||||
result.add(interfaceList);
|
||||
|
||||
// 接口日期保存
|
||||
List<Object> dateList = interfaceSummaryList.stream().map(WxDataCubeInterfaceResult::getRefDate)
|
||||
.collect(Collectors.toList());
|
||||
result.add(dateList);
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error(" 获取公众号统计数据报错", e);
|
||||
return R.failed("获取公众号数据失败:" + e.getError().getErrorMsg());
|
||||
}
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.pig4cloud.pigx.mp.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountTag;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountTagMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxAccountTagService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.tag.WxUserTag;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 微信账户标签服务实现类
|
||||
*
|
||||
* @author lishangbu
|
||||
* @date 2021/12/31
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WxAccountTagServiceImpl extends ServiceImpl<WxAccountTagMapper, WxAccountTag>
|
||||
implements WxAccountTagService {
|
||||
|
||||
private final WxAccountMapper accountMapper;
|
||||
|
||||
/**
|
||||
* 保存账户标签
|
||||
* @param accountTag 账户标签
|
||||
* @return 保存成功true保存失败false
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public WxAccountTag saveAccountTag(WxAccountTag accountTag) {
|
||||
WxAccountTag wxAccountTag = baseMapper
|
||||
.selectOne(Wrappers.<WxAccountTag>lambdaQuery().eq(WxAccountTag::getTag, accountTag.getTag()));
|
||||
Assert.isNull(wxAccountTag, StrUtil.format("名称为[{}]的标签已经存在", accountTag.getTag()));
|
||||
|
||||
// 调用微信公众号接口删除
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(accountTag.getWxAccountAppid());
|
||||
WxUserTag wxUserTag = wxMpService.getUserTagService().tagCreate(accountTag.getTag());
|
||||
accountTag.setTagId(wxUserTag.getId());
|
||||
|
||||
// 查询公众号详细信息
|
||||
WxAccount wxAccount = accountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAppid, accountTag.getWxAccountAppid()));
|
||||
accountTag.setWxAccountId(wxAccount.getId());
|
||||
accountTag.setWxAccountAppid(wxAccount.getAppid());
|
||||
accountTag.setWxAccountName(wxAccount.getName());
|
||||
|
||||
baseMapper.insert(accountTag);
|
||||
return accountTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新账户标签
|
||||
* @param accountTag 待更新的账户标签
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public WxAccountTag updateAccountTag(WxAccountTag accountTag) {
|
||||
baseMapper.updateById(accountTag);
|
||||
|
||||
// 调用微信公众号接口更新
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(accountTag.getWxAccountAppid());
|
||||
wxMpService.getUserTagService().tagUpdate(accountTag.getTagId(), accountTag.getTag());
|
||||
return accountTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean removeAccountTagById(WxAccountTag tag) {
|
||||
WxAccountTag wxAccountTag = baseMapper.selectById(tag.getId());
|
||||
Assert.notNull(wxAccountTag, "要删除的标签不存在");
|
||||
|
||||
// 调用微信公众号接口删除
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(tag.getWxAccountAppid());
|
||||
wxMpService.getUserTagService().tagDelete(wxAccountTag.getTagId());
|
||||
return removeById(wxAccountTag.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步账户标签
|
||||
* @param appId appId
|
||||
* @return Boolean
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean syncAccountTags(String appId) {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
List<WxUserTag> wxUserTags = wxMpService.getUserTagService().tagGet();
|
||||
|
||||
// 删除旧数据
|
||||
baseMapper.delete(Wrappers.<WxAccountTag>lambdaQuery().eq(WxAccountTag::getWxAccountAppid, appId));
|
||||
|
||||
WxAccount wxAccount = accountMapper.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAppid, appId));
|
||||
for (WxUserTag wxUserTag : wxUserTags) {
|
||||
WxAccountTag tag = new WxAccountTag();
|
||||
tag.setTag(wxUserTag.getName());
|
||||
tag.setTagId(wxUserTag.getId());
|
||||
tag.setWxAccountId(wxAccount.getId());
|
||||
tag.setWxAccountAppid(wxAccount.getAppid());
|
||||
tag.setWxAccountName(wxAccount.getName());
|
||||
baseMapper.insert(tag);
|
||||
}
|
||||
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.pig4cloud.pigx.mp.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAutoReply;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAutoReplyMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxAutoReplyService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 消息自动回复
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-04-18 15:40:39
|
||||
*/
|
||||
@Service
|
||||
public class WxAutoReplyServiceImpl extends ServiceImpl<WxAutoReplyMapper, WxAutoReply> implements WxAutoReplyService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
package com.pig4cloud.pigx.mp.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMpMenu;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxMenuMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxMenuService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.mp.api.WxMpMenuService;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 微信菜单业务
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2019-03-27 20:45:18
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class WxMenuServiceImpl extends ServiceImpl<WxMenuMapper, WxMpMenu> implements WxMenuService {
|
||||
|
||||
private static final String PUB_ED = "1";
|
||||
|
||||
private final WxAccountMapper wxAccountMapper;
|
||||
|
||||
/**
|
||||
* 新增微信公众号按钮
|
||||
* @param wxMenus json
|
||||
* @param appId 公众号
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean save(JSONObject wxMenus, String appId) {
|
||||
baseMapper.delete(Wrappers.<WxMpMenu>lambdaQuery().eq(WxMpMenu::getWxAccountAppid, appId));
|
||||
|
||||
WxAccount wxAccount = wxAccountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAppid, appId));
|
||||
WxMpMenu wxMpMenu = new WxMpMenu();
|
||||
wxMpMenu.setMenu(wxMenus.toStringPretty());
|
||||
wxMpMenu.setWxAccountId(wxAccount.getId());
|
||||
wxMpMenu.setWxAccountAppid(wxAccount.getAppid());
|
||||
wxMpMenu.setWxAccountName(wxAccount.getName());
|
||||
baseMapper.insert(wxMpMenu);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布到微信
|
||||
* @param appId 公众号信息
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public R push(String appId) {
|
||||
List<WxMpMenu> wxMpMenuList = baseMapper
|
||||
.selectList(Wrappers.<WxMpMenu>lambdaQuery().eq(WxMpMenu::getWxAccountAppid, appId));
|
||||
|
||||
if (CollUtil.isEmpty(wxMpMenuList)) {
|
||||
return R.failed("微信菜单配置未保存,不能发布");
|
||||
}
|
||||
WxMpMenu wxMpMenu = wxMpMenuList.get(0);
|
||||
// 判断是否发布
|
||||
if (PUB_ED.equals(wxMpMenu.getPubFlag())) {
|
||||
return R.failed("微信菜单配置已发布,不要重复发布");
|
||||
}
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMenuService menuService = wxMpService.getMenuService();
|
||||
|
||||
// 给数据库保存的加一层
|
||||
try {
|
||||
menuService.menuCreate(wxMpMenu.getMenu());
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error("发布微信菜单失败", e.getError().getErrorMsg());
|
||||
return R.failed(e.getError().getErrorMsg());
|
||||
}
|
||||
|
||||
// 更新菜单发布标志
|
||||
wxMpMenu.setPubFlag(PUB_ED);
|
||||
baseMapper.updateById(wxMpMenu);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过appid 查询菜单信息
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public R getByAppId(String appId) {
|
||||
List<WxMpMenu> wxMpMenuList = baseMapper
|
||||
.selectList(Wrappers.<WxMpMenu>lambdaQuery().eq(WxMpMenu::getWxAccountAppid, appId));
|
||||
|
||||
if (CollUtil.isEmpty(wxMpMenuList)) {
|
||||
return R.ok();
|
||||
}
|
||||
return R.ok(wxMpMenuList.get(0).getMenu());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过appid 删除菜单
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public R delete(String appId) {
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(appId);
|
||||
WxMpMenuService menuService = wxMpService.getMenuService();
|
||||
try {
|
||||
menuService.menuDelete();
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
log.error("微信菜单删除失败", e.getError().getErrorMsg());
|
||||
return R.failed(e.getError().getErrorMsg());
|
||||
}
|
||||
|
||||
baseMapper.delete(Wrappers.<WxMpMenu>lambdaQuery().eq(WxMpMenu::getWxAccountAppid, appId));
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.pig4cloud.pigx.mp.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.mp.config.WxMpInitConfigRunner;
|
||||
import com.pig4cloud.pigx.mp.constant.MsgTypeEnum;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccount;
|
||||
import com.pig4cloud.pigx.mp.entity.WxAccountFans;
|
||||
import com.pig4cloud.pigx.mp.entity.WxMsg;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxAccountMapper;
|
||||
import com.pig4cloud.pigx.mp.mapper.WxMsgMapper;
|
||||
import com.pig4cloud.pigx.mp.service.WxMsgService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.mp.api.WxMpKefuService;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 微信消息
|
||||
*
|
||||
* @author JL
|
||||
* @date 2019-05-28 16:12:10
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class WxMsgServiceImpl extends ServiceImpl<WxMsgMapper, WxMsg> implements WxMsgService {
|
||||
|
||||
private final WxAccountFansMapper accountFansMapper;
|
||||
|
||||
private final WxAccountMapper accountMapper;
|
||||
|
||||
private final WxMsgMapper msgMapper;
|
||||
|
||||
/**
|
||||
* 保存信息并向用户推送
|
||||
* @param wxMsg 推送消息内容
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public R saveAndPushMsg(WxMsg wxMsg) {
|
||||
// 查询用户详细
|
||||
WxAccountFans wxUser = accountFansMapper.selectById(wxMsg.getWxUserId());
|
||||
WxAccount wxAccount = accountMapper
|
||||
.selectOne(Wrappers.<WxAccount>lambdaQuery().eq(WxAccount::getAppid, wxMsg.getAppId()));
|
||||
|
||||
// 维护消息-用户
|
||||
wxMsg.setNickName(wxAccount.getName());
|
||||
wxMsg.setHeadimgUrl(wxUser.getHeadimgUrl());
|
||||
wxMsg.setType(MsgTypeEnum.MP2USER.getType());
|
||||
|
||||
// 维护消息-公众号
|
||||
wxMsg.setAppLogo(wxAccount.getQrUrl());
|
||||
wxMsg.setAppName(wxAccount.getName());
|
||||
|
||||
WxMpKefuMessage wxMpKefuMessage = null;
|
||||
if (WxConsts.KefuMsgType.TEXT.equals(wxMsg.getRepType())) {
|
||||
wxMsg.setRepContent(wxMsg.getRepContent());
|
||||
wxMpKefuMessage = WxMpKefuMessage.TEXT().build();
|
||||
wxMpKefuMessage.setContent(wxMsg.getRepContent());
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.IMAGE.equals(wxMsg.getRepType())) {// 图片
|
||||
wxMsg.setRepName(wxMsg.getRepName());
|
||||
wxMsg.setRepUrl(wxMsg.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxMsg.getRepMediaId());
|
||||
wxMpKefuMessage = WxMpKefuMessage.IMAGE().build();
|
||||
wxMpKefuMessage.setMediaId(wxMsg.getRepMediaId());
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.VOICE.equals(wxMsg.getRepType())) {
|
||||
wxMsg.setRepName(wxMsg.getRepName());
|
||||
wxMsg.setRepUrl(wxMsg.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxMsg.getRepMediaId());
|
||||
wxMpKefuMessage = WxMpKefuMessage.VOICE().build();
|
||||
wxMpKefuMessage.setMediaId(wxMsg.getRepMediaId());
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.VIDEO.equals(wxMsg.getRepType())) {
|
||||
wxMsg.setRepName(wxMsg.getRepName());
|
||||
wxMsg.setRepDesc(wxMsg.getRepDesc());
|
||||
wxMsg.setRepUrl(wxMsg.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxMsg.getRepMediaId());
|
||||
wxMpKefuMessage = WxMpKefuMessage.VIDEO().build();
|
||||
wxMpKefuMessage.setMediaId(wxMsg.getRepMediaId());
|
||||
wxMpKefuMessage.setTitle(wxMsg.getRepName());
|
||||
wxMpKefuMessage.setDescription(wxMsg.getRepDesc());
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.MUSIC.equals(wxMsg.getRepType())) {
|
||||
wxMsg.setRepName(wxMsg.getRepName());
|
||||
wxMsg.setRepDesc(wxMsg.getRepDesc());
|
||||
wxMsg.setRepUrl(wxMsg.getRepUrl());
|
||||
wxMsg.setRepHqUrl(wxMsg.getRepHqUrl());
|
||||
wxMpKefuMessage = WxMpKefuMessage.MUSIC().build();
|
||||
wxMpKefuMessage.setTitle(wxMsg.getRepName());
|
||||
wxMpKefuMessage.setDescription(wxMsg.getRepDesc());
|
||||
wxMpKefuMessage.setMusicUrl(wxMsg.getRepUrl());
|
||||
wxMpKefuMessage.setHqMusicUrl(wxMsg.getRepHqUrl());
|
||||
wxMpKefuMessage.setThumbMediaId(wxMsg.getRepThumbMediaId());
|
||||
}
|
||||
|
||||
if (WxConsts.KefuMsgType.NEWS.equals(wxMsg.getRepType())) {
|
||||
List<WxMpKefuMessage.WxArticle> list = new ArrayList<>();
|
||||
JSONArray jsonArray = JSONUtil.parseArray(wxMsg.getContent());
|
||||
for (Object obj : jsonArray) {
|
||||
JSONObject jsonObject = (JSONObject) obj;
|
||||
WxMpKefuMessage.WxArticle t = new WxMpKefuMessage.WxArticle();
|
||||
t.setTitle(jsonObject.getStr("title"));
|
||||
t.setDescription(jsonObject.getStr("digest"));
|
||||
t.setPicUrl(jsonObject.getStr("thumbUrl"));
|
||||
t.setUrl(jsonObject.getStr("url"));
|
||||
list.add(t);
|
||||
}
|
||||
wxMsg.setRepName(wxMsg.getRepName());
|
||||
wxMsg.setRepDesc(wxMsg.getRepDesc());
|
||||
wxMsg.setRepUrl(wxMsg.getRepUrl());
|
||||
wxMsg.setRepMediaId(wxMsg.getRepMediaId());
|
||||
wxMsg.setContent(wxMsg.getContent());
|
||||
wxMpKefuMessage = WxMpKefuMessage.NEWS().build();
|
||||
wxMpKefuMessage.setArticles(list);
|
||||
}
|
||||
|
||||
if (wxMpKefuMessage == null) {
|
||||
return R.failed("非法消息类型");
|
||||
}
|
||||
|
||||
WxMpService wxMpService = WxMpInitConfigRunner.getMpServices().get(wxMsg.getAppId());
|
||||
WxMpKefuService wxMpKefuService = wxMpService.getKefuService();
|
||||
wxMpKefuMessage.setToUser(wxUser.getOpenid());
|
||||
try {
|
||||
wxMpKefuService.sendKefuMessage(wxMpKefuMessage);
|
||||
msgMapper.insert(wxMsg);
|
||||
return R.ok(wxMsg);
|
||||
}
|
||||
catch (WxErrorException e) {
|
||||
return R.failed(e.getError().getErrorMsg());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
server:
|
||||
port: 6000
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: @artifactId@
|
||||
cloud:
|
||||
nacos:
|
||||
username: @nacos.username@
|
||||
password: @nacos.password@
|
||||
discovery:
|
||||
server-addr: ${NACOS_HOST:pigx-register}:${NACOS_PORT:8848}
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
config:
|
||||
import:
|
||||
- optional:nacos:application-@profiles.active@.yml
|
||||
- optional:nacos:${spring.application.name}-@profiles.active@.yml
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
~
|
||||
~ Redistribution and use in source and binary forms, with or without
|
||||
~ modification, are permitted provided that the following conditions are met:
|
||||
~
|
||||
~ Redistributions of source code must retain the above copyright notice,
|
||||
~ this list of conditions and the following disclaimer.
|
||||
~ Redistributions in binary form must reproduce the above copyright
|
||||
~ notice, this list of conditions and the following disclaimer in the
|
||||
~ documentation and/or other materials provided with the distribution.
|
||||
~ Neither the name of the pig4cloud.com developer nor the names of its
|
||||
~ contributors may be used to endorse or promote products derived from
|
||||
~ this software without specific prior written permission.
|
||||
~ Author: lengleng (wangiegie@gmail.com)
|
||||
-->
|
||||
|
||||
<!--
|
||||
小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
|
||||
<properties>
|
||||
<log-path>/Users/lengleng</log-path>
|
||||
</properties>
|
||||
1. 其他模块加日志输出,直接copy本文件放在resources 目录即可
|
||||
2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
|
||||
-->
|
||||
<configuration debug="false" scan="false">
|
||||
<property name="log.path" value="logs/${project.artifactId}"/>
|
||||
<!-- 彩色日志格式 -->
|
||||
<property name="CONSOLE_LOG_PATTERN"
|
||||
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||
<!-- 彩色日志依赖的渲染类 -->
|
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
|
||||
<conversionRule conversionWord="wex"
|
||||
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
|
||||
<conversionRule conversionWord="wEx"
|
||||
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
||||
<!-- Console log output -->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Log file debug output -->
|
||||
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/debug.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Log file error output -->
|
||||
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>ERROR</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<logger name="org.activiti.engine.impl.db" level="DEBUG">
|
||||
<appender-ref ref="debug"/>
|
||||
</logger>
|
||||
|
||||
<!--nacos 心跳 INFO 屏蔽-->
|
||||
<logger name="com.alibaba.nacos" level="OFF">
|
||||
<appender-ref ref="error"/>
|
||||
</logger>
|
||||
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="console"/>
|
||||
<appender-ref ref="debug"/>
|
||||
<appender-ref ref="error"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.pig4cloud.pigx.mp.mapper.WxAccountFansMapper">
|
||||
|
||||
<resultMap id="wxAccountFansMap" type="com.pig4cloud.pigx.mp.entity.WxAccountFans">
|
||||
<id property="id" column="id"/>
|
||||
<result property="openid" column="openid"/>
|
||||
<result property="subscribeStatus" column="subscribe_status"/>
|
||||
<result property="subscribeTime" column="subscribe_time"/>
|
||||
<result property="nickname" column="nickname"/>
|
||||
<result property="gender" column="gender"/>
|
||||
<result property="language" column="language"/>
|
||||
<result property="country" column="country"/>
|
||||
<result property="province" column="province"/>
|
||||
<result property="city" column="city"/>
|
||||
<result property="headimgUrl" column="headimg_url"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="wxAccountId" column="wx_account_id"/>
|
||||
<result property="wxAccountAppid" column="wx_account_appid"/>
|
||||
<result property="wxAccountName" column="wx_account_name"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
</resultMap>
|
||||
</mapper>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.pig4cloud.pigx.mp.mapper.WxAccountMapper">
|
||||
|
||||
<resultMap id="wxAccountMap" type="com.pig4cloud.pigx.mp.entity.WxAccount">
|
||||
<id property="id" column="id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="account" column="account"/>
|
||||
<result property="appid" column="appid"/>
|
||||
<result property="appsecret" column="appsecret"/>
|
||||
<result property="url" column="url"/>
|
||||
<result property="token" column="token"/>
|
||||
<result property="aeskey" column="aeskey"/>
|
||||
<result property="qrUrl" column="qr_url"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
</resultMap>
|
||||
</mapper>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2018-2019
|
||||
~ All rights reserved, Designed By www.joolun.com
|
||||
-->
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.pig4cloud.pigx.mp.mapper.WxAutoReplyMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.pig4cloud.pigx.mp.mapper.WxMenuMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2018-2019
|
||||
~ All rights reserved, Designed By www.joolun.com
|
||||
-->
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.pig4cloud.pigx.mp.mapper.WxMsgMapper">
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user