初始化

This commit is contained in:
Leo
2023-12-21 18:02:13 +08:00
commit f2ef3b397d
19 changed files with 2869 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

118
pom.xml Normal file
View File

@@ -0,0 +1,118 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.van333</groupId>
<artifactId>MT2QL</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MT2QL</name>
<description>MT2QL</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.mysql</groupId>-->
<!-- <artifactId>mysql-connector-j</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- </dependency>-->
<!-- 自己加的 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.14</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
<!-- 自己加的 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>cn.van333.mt2ql.Mt2QlApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,17 @@
package cn.van333.mt2ql;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
@MapperScan("cn.van333.mt2ql.wxMessage.mapper")
public class Mt2QlApplication {
public static void main(String[] args) {
SpringApplication.run(Mt2QlApplication.class, args);
}
}

View File

@@ -0,0 +1,67 @@
package cn.van333.mt2ql.wxMessage;
import cn.van333.mt2ql.wxMessage.model.WxMessage;
import cn.van333.mt2ql.wxMessage.utils.WxMessageConsumer;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Leo
* @version 1.0
* @create 2023/12/18 0018 下午 05:12
* @description
*/
@Controller
@RequestMapping("/wx")
@RestController
public class WXListener {
private static final Logger logger = LoggerFactory.getLogger(WXListener.class);
@Autowired
private WxMessageConsumer wxMessageConsumer;
/**
* {
* "event": 10009,
* "wxid": "wxid_kr145nk7l0an31",
* "data": {
* "type": "D0003",* "des": "鏀跺埌娑堟伅",
* "data": {
* "timeStamp": "1702951964728",
* "fromType": 1,
* "msgType": 1,
* "msgSource": 0,
* "fromWxid": "wxid_ytpc72mdoskt22",
* "finalFromWxid": "",
* "atWxidList": [],
* "silence": 0,
* "membercount": 0,
* "signature": "v1_MllZwZMZ",
* "msg": "785",
* "msgBase64": "Nzg1"
* },
* "timestamp": "1702951964740",
* "wxid": "wxid_kr145nk7l0an31",
* "port": 16888,
* "pid": 10468,
* "flag": "7777"
* }
* }
**/
@RequestMapping("/message")
public String message(@RequestBody String requestBody) {
WxMessage message = JSONObject.parseObject(requestBody, WxMessage.class);
wxMessageConsumer.consume(message);
return "OK";
}
}

View File

@@ -0,0 +1,60 @@
package cn.van333.mt2ql.wxMessage.config;
import cn.van333.mt2ql.wxMessage.utils.Threads;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author Leo
* @version 1.0
* @create 2023/12/19 0019 上午 10:58
* @description
*/
@Configuration
public class ThreadPoolConfig {
// 核心线程池大小
private final int corePoolSize = 50;
// 最大可创建的线程数
private final int maxPoolSize = 200;
// 队列最大长度
private final int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间
private final int keepAliveSeconds = 300;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 执行周期性或定时任务
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
Threads.printException(r, t);
}
};
}
}

View File

@@ -0,0 +1,99 @@
package cn.van333.mt2ql.wxMessage.enums;
/**
* @author Leo
* @version 1.0
* @create 2023/12/19 0019 上午 10:27
* @description
*/
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public enum EventType implements IEnum {
/**
* 账号变动事件 (10014)
* 收到群聊消息 (10008)
* 收到私聊消息 (10009)
* 自己发出消息 (10010)
* 转账事件 (10006)
* 撤回事件 (10013)
* 好友请求 (10011)
* 支付事件 (10007)
*/
ACCOUNT_CHANGE(10014, "账号变动事件"),
SELF_MESSAGE(10010, "自己发出消息"),
TRANSFER_EVENT(10006, "转账事件"),
PAY_EVENT(10007, "支付事件"),
GROUP_MESSAGE(10008, "收到群聊消息"),
PRIVATE_MESSAGE(10009, "收到私聊消息"),
REVOKE_EVENT(10013, "撤回事件"),
FRIEND_REQUEST(10011, "好友请求");
private final int key;
private final String name;
EventType(int key, String name) {
this.key = key;
this.name = name;
}
public static EventType get(int key) {
for (EventType e : EventType.values()) {
if (e.getKey() == key) {
return e;
}
}
return null;
}
public static String getName(Integer key) {
//if (Object.isNotEmpty(key)) {
EventType[] items = EventType.values();
for (EventType item : items) {
if (item.getKey() == key) {
return item.getName();
}
}
//}
return "";
}
public static Map<String, String> getKeyVlue() {
Map<String, String> map = new HashMap<>();
EventType[] items = EventType.values();
for (EventType item : items) {
map.put(item.getKey() + "", item.getName());
}
return map;
}
public static List<Map<String, Object>> getSelectItems() {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
EventType[] items = EventType.values();
for (EventType item : items) {
Map<String, Object> map = new HashMap<>();
map.put("label", item.getName());
map.put("value", item.getKey());
result.add(map);
}
return result;
}
@Override
@JsonValue
public Integer getKey() {
return key;
}
@Override
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,87 @@
package cn.van333.mt2ql.wxMessage.enums;
/**
* @author Leo
* @version 1.0
* @create 2023/12/19 0019 上午 10:27
* @description
*/
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public enum FromType implements IEnum {
/**
* fromType 来源类型1|私聊 2|群聊 3|公众号
*/
PRIVATE(1, "私聊"),
GROUP(2, "群聊"),
MP(3, "公众号");
private final int key;
private final String name;
FromType(int key, String name) {
this.key = key;
this.name = name;
}
public static FromType get(int key) {
for (FromType e : FromType.values()) {
if (e.getKey() == key) {
return e;
}
}
return null;
}
public static String getName(Integer key) {
//if (Object.isNotEmpty(key)) {
FromType[] items = FromType.values();
for (FromType item : items) {
if (item.getKey() == key) {
return item.getName();
}
}
//}
return "";
}
public static Map<String, String> getKeyVlue() {
Map<String, String> map = new HashMap<>();
FromType[] items = FromType.values();
for (FromType item : items) {
map.put(item.getKey() + "", item.getName());
}
return map;
}
public static List<Map<String, Object>> getSelectItems() {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
FromType[] items = FromType.values();
for (FromType item : items) {
Map<String, Object> map = new HashMap<>();
map.put("label", item.getName());
map.put("value", item.getKey());
result.add(map);
}
return result;
}
@Override
@JsonValue
public Integer getKey() {
return key;
}
@Override
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,14 @@
package cn.van333.mt2ql.wxMessage.enums;
/**
* @author Leo
* @version 1.0
* @create 2023/12/19 0019 上午 10:33
* @description
*/
public interface IEnum {
Integer getKey();
String getName();
}

View File

@@ -0,0 +1,83 @@
package cn.van333.mt2ql.wxMessage.enums;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Leo
* @version 1.0
* @create 2023/12/19 0019 下午 02:31
* @description
*/
public enum MsgTypeEnum implements IEnum {
/**
msgType 消息类型1|文本 3|图片 34|语音 42|名片 43|视频 47|动态表情 48|地理位置 49|分享链接或附件 2001|红包 2002|小程序 2003|群邀请 10000|系统消息
* */
TEXT(1, "文本"),
IMAGE(3, "图片"),
VOICE(34, "语音"),
VIDEO(43, "视频"),
SHARE(49, "分享"),
LOCATION(48, "位置"),
REDPACKET(2001, "红包"),
MINIPROGRAM(2002, "小程序"),
GROUP_INVITE(2003, "群邀请"),
SYSTEM(10000, "系统消息"),
;
private final int key;
private final String name;
MsgTypeEnum(int key, String name) {
this.key = key;
this.name = name;
}
public static String getName(Integer key) {
for (MsgTypeEnum msgTypeEnum : MsgTypeEnum.values()) {
if (msgTypeEnum.key == key) {
return msgTypeEnum.name;
}
}
return null;
}
public static Map<String, String> getKeyVlue() {
Map<String, String> map = new HashMap<>();
for (MsgTypeEnum msgTypeEnum : MsgTypeEnum.values()) {
map.put(msgTypeEnum.key + "", msgTypeEnum.name);
}
return map;
}
public static List<Map<String, Object>> getSelectItems() {
List<Map<String, Object>> list = new ArrayList<>();
for (MsgTypeEnum msgTypeEnum : MsgTypeEnum.values()) {
Map<String, Object> map = new HashMap<>();
map.put("key", msgTypeEnum.key);
map.put("value", msgTypeEnum.name);
list.add(map);
}
return list;
}
public static MsgTypeEnum get(int key) {
for (MsgTypeEnum msgTypeEnum : MsgTypeEnum.values()) {
if (msgTypeEnum.key == key) {
return msgTypeEnum;
}
}
return null;
}
public Integer getKey() {
return key;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,32 @@
package cn.van333.mt2ql.wxMessage.model;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
/**
* @author Leo
* @version 1.0
* @create 2023/12/18 0018 下午 05:52
* @description
*/
@Data
public class WxMessage {
//{
// "event": 10014, //事件的id可用来区分是什么事件
// "wxid": "wxid_3sq4tklb6c3121", //收到这条事件的微信
// "data": {} //事件的主要内容,不同事件,具体对象参数也不尽相同
//}
private Integer event;
private String wxid;
private JSONObject data;
public WxMessage() {
}
public WxMessage(Integer event, String wxid, JSONObject data) {
this.event = event;
this.wxid = wxid;
this.data = data;
}
}

View File

@@ -0,0 +1,256 @@
package cn.van333.mt2ql.wxMessage.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* spring redis 工具类
*
* @author ruoyi
**/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key) {
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection) {
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 重新set缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long reSetCacheList(final String key, final List<T> dataList) {
this.deleteObject(key);
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey) {
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}

View File

@@ -0,0 +1,81 @@
package cn.van333.mt2ql.wxMessage.utils;
/**
* @author Leo
* @version 1.0
* @create 2023/12/19 0019 上午 11:01
* @description
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
/**
* 线程相关工具类.
*
* @author ruoyi
*/
public class Threads {
private static final Logger logger = LoggerFactory.getLogger(Threads.class);
/**
* sleep等待,单位为毫秒
*/
public static void sleep(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
return;
}
}
/**
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍然超時,則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
public static void shutdownAndAwaitTermination(ExecutorService pool) {
if (pool != null && !pool.isShutdown()) {
pool.shutdown();
try {
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
logger.info("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
logger.error(t.getMessage(), t);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,289 @@
package cn.van333.mt2ql.wxMessage.utils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpRequest;
import cn.van333.mt2ql.wxMessage.enums.EventType;
import cn.van333.mt2ql.wxMessage.model.WxMessage;
import cn.van333.mt2ql.wxMessage.model.WxMessageDataForChat;
import cn.van333.mt2ql.wxMessage.model.WxUser;
import cn.van333.mt2ql.wxMessage.service.WxMessageDataForChatService;
import cn.van333.mt2ql.wxMessage.service.WxUserService;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static cn.hutool.core.thread.ThreadUtil.sleep;
/**
* @author Leo
* @version 1.0
* @create 2023/12/19 0019 上午 11:03
* @description
*/
@Component
public class WxMessageConsumer {
public static final String QL_BASE_URL = "http://134.175.126.60:45700";
public static final String GET_TOKEN = QL_BASE_URL + "/open/auth/token";
//client_id
public static final String CLIENT_ID = "Ouv_S9gk5LpV";
//client_secret
public static final String CLIENT_SECRET = "1pLjAIfBBzu1_UA9q-hOj778";
public static final String QL_TOKEN_KEY = "QL_TOKEN_KEY";
/**/
private static final Logger logger = LoggerFactory.getLogger(WxMessageConsumer.class);
private static final RedisCache redisCache = SpringUtil.getBean(RedisCache.class);
/**
* 临时参数
* 每次扣费
*/
private static final BigDecimal priceOfMT20 = new BigDecimal("0.2");
private final WxMessageDataForChatService wxMessageDataForChatService;
private final WxUserService wxUserService;
@Autowired
public WxMessageConsumer(@Lazy WxMessageDataForChatService wxMessageDataForChatService, @Lazy WxUserService wxUserService) {
this.wxMessageDataForChatService = wxMessageDataForChatService;
this.wxUserService = wxUserService;
}
@Async("threadPoolTaskExecutor")
public void consume(WxMessage wxMessage) {
logger.info("接收到消息 : {}", wxMessage);
if (wxMessage.getEvent() == null) {
return;
}
/**
* 需要处理 私聊 和 转账消息
* 其他消息暂时不处理
* 私聊需要解析是否美团领券
* 转账需要对接会员系统
*
* */
Integer event = wxMessage.getEvent();
if (event.equals(EventType.PRIVATE_MESSAGE.getKey())) {
handlePrivateMessage(wxMessage);
}
}
/**
* 处理私聊消息
*
* @param wxMessage
*/
private void handlePrivateMessage(WxMessage wxMessage) {
/**
* 接收到消息 : WxMessage(event=10009, wxid=wxid_kr145nk7l0an31, data={"type":"D0003","des":"收到消息","data":{"timeStamp":"1703128368100","fromType":1,"msgT两次ype":1,"msgSource":0,"fromWxid":"wxid_ytpc72mdoskt22","finalFromWxid":"","atWxidList":[],"silence":0,"membercount":0,"signature":"v1_vXrWK/iB","msg":"嗨鲁个迷紫123","msgBase64":"5Zeo6bKB5Liq6L+357SrMTIz"},"timestamp":"1703128368112","wxid":"wxid_kr145nk7l0an31","port":16888,"pid":10468,"flag":"7777"})
* 需要get 两次 data 字段*/
JSONObject data = wxMessage.getData().getJSONObject("data");
if (data == null) {
return;
}
System.out.println("+++++++++++"+getToken());
/**{"type":"D0003","des":"收到消息","data":{"timeStamp":"1702957325031","fromType":1,"msgType":1,"msgSource":0,"fromWxid":"wxid_ytpc72mdoskt22","finalFromWxid":"","atWxidList":[],"silence":0,"membercount":0,"signature":"v1_OJXJYpvM","msg":"在不","msgBase64":"5Zyo5LiN"},"timestamp":"1702957325041","wxid":"wxid_kr145nk7l0an31","port":16888,"pid":10468,"flag":"7777"}
* */
WxMessageDataForChat wxMessageDataForChat = data.to(WxMessageDataForChat.class);
// 做业务处理
logger.info("处理消息: {}", wxMessageDataForChat.toString());
/**
* timeStamp 收到这条消息的13位现行时间戳
* fromType 来源类型1|私聊 2|群聊 3|公众号
* msgType 消息类型1|文本 3|图片 34|语音 42|名片 43|视频 47|动态表情 48|地理位置 49|分享链接或附件 2001|红包 2002|小程序 2003|群邀请 10000|系统消息
* msgSource 消息来源0|别人发送 1|自己手机发送
* fromWxid fromType=1时为好友wxidfromType=2时为群wxidfromType=3时公众号wxid
* finalFromWxid 仅fromType=2时有效为群内发言人wxid
* atWxidList 仅fromType=2且msgSource=0时有效为消息中艾特人wxid列表
* silence 仅fromType=2时有效0
* membercount 仅fromType=2时有效群成员数量
* signature 消息签名
* msg 消息内容
* msgBase64 消息内容的Base64
* */
if (Util.isAnyEmpty(wxMessageDataForChat.getMsg(), wxMessageDataForChat.getFromwxid(), wxMessageDataForChat.getFromtype())) {
logger.info("消息内容为空,不处理");
return;
}
String msg = wxMessageDataForChat.getMsg();
/**
* https://i.meituan.com/mttouch/page/account
* ?userId=3822095266
* &token=AgHdIkm2tAGHc9SQSiG7M8xCx1LbTue9D2HPOAun2eYl3ou7BeEw1uGrGZH-DxmEiUgsbA1v9SM4DQAAAAC6HAAAz0rTXmkB_CIHin08hCu68mFv5k6nUc2q6_CfZqEdBcngRK_xD8Sx5fE4rfdq-yAJ
* */
if (msg.startsWith("美团 20-7 ")) {
logger.info("处理美团的消息");
if (msg.contains("https://i.meituan.com/mttouch/page/account")) {
String wxid = null;
if (wxMessageDataForChat.getFromtype() == 1) {
wxid = wxMessageDataForChat.getFromwxid();
} else if (wxMessageDataForChat.getFromtype() == 2) {
wxid = wxMessageDataForChat.getFinalfromwxid();
}
String token = msg.substring(msg.indexOf("token=") + 6, msg.indexOf("&"));
String userId = msg.substring(msg.indexOf("userId=") + 7);
logger.info("token: {}, userId: {}", token, userId);
mt20(wxid, userId, token);
//if (runResult) {
// logger.info("领券成功");
// String newMsg = msg.replace("https://i.meituan.com/mttouch/page/account", "https://i.meituan.com/mttouch/page/account?userId=" + userId + "&token=" + token);
// wxMessageDataForChat.setMsg(newMsg);
// wxMessageDataForChatService.updateById(wxMessageDataForChat);
//} else {
// logger.info("领券失败");
//}
}
}
wxMessageDataForChatService.save(wxMessageDataForChat);
}
/**
* @param userId
* @param token
* @return
* @throws
* @description
*/
private String mt20(String wxid, String userId, String token) {
/**
* 1 查询用户余额
* 2 调用青龙的添加环境变量
* 3 执行美团领券
* 4 删除环境变量
* 5 改写返回的消息内容返回给用户
* */
logger.info("查询用户余额");
HashMap<String, Object> checkYuE = checkYuE(wxid);
Boolean isRun = (Boolean) checkYuE.get("isRun");
String info = (String) checkYuE.get("info");
BigDecimal yuE = (BigDecimal) checkYuE.get("yuE");
// 余额可以支持一次扣费
if (isRun) {
// 调用青龙 成功
return runQL(token, wxid, 1);
} else {
// 调用青龙 失败
logger.info("调用青龙失败");
return info;
}
}
/**
* @param wxid
* @return
* @throws
* @description 根据 wxid 查询余额
*/
private HashMap<String, Object> checkYuE(String wxid) {
HashMap<String, Object> result = new HashMap<>();
BigDecimal yuE = BigDecimal.ZERO;
String info = "";
Boolean isRun = false;
WxUser wxUser = wxUserService.getOne(Wrappers.query(new WxUser()).eq("wxid", wxid));
if (Util.isEmpty(wxUser)) {
info = "未进行过充值,请先充值后使用。";
}
// 如果余额小于等于零
if (wxUser.getMoneyShengyu().compareTo(BigDecimal.ZERO) <= 0) {
info = "账户余额不足,请先充值后使用。";
}
if (wxUser.getMoneyShengyu().compareTo(priceOfMT20) < 0) {
info = "剩余余额不足以支持本次扣费,请先充值后使用。";
} else {
isRun = true;
}
// 返回结果
result.put("yuE", yuE);
result.put("info", info);
result.put("isRun", isRun);
return result;
}
/**
* @param remark
* @param time 调用次数,后期可以改成包月还是一次 ,目前都是 1
* @param token
* @return
* @throws
* @description
*/
private String runQL(String token, String remark, Integer time) {
/**
* 1. 在系统设置 -> 应用设置 -> 添加应用权限目前支持5个模块可以选择多个模块。选择一个模块之后可读写此模块的所有接口。
* 2. 使用生成的 client_id 和 client_secret 请求获取token接口 http://localhost:5700/open/auth/token?client_id=xxxxxx&client_secret=xxxxxxxx
* 3. 上面接口返回的token有效期为30天可用于请求青龙的接口 curl 'http://localhost:5700/open/envs?searchValue=&t=1630032278171' -H 'Authorization: Bearer
* 接口返回的token'
* 4. openapi的接口与系统正常接口的区别就是青龙里的是/api/envsopenapi是/open/envs即就是青龙接口中的api换成open
* */
//String responseStr = HttpRequest.post(QL_BASE_URL + getToken())
// .body(JSON.toJSONString(jsonMap))//头信息,多个头信息多次调用此方法即可
// .execute().body();
System.out.println("+++++++++++"+getToken());
return null;
}
private String getToken() {
String token = redisCache.getCacheObject(QL_TOKEN_KEY);
if (StrUtil.isNotEmpty(token)) {
return token;
} else {
//HashMap<String, String> map = new HashMap<>();
//map.put("client_id", CLIENT_ID);
//map.put("client_secret", CLIENT_SECRET);
//String jsonStr = JSON.toJSONString(map);
for (int i = 0; i < 3; i++) {
//* 2. 使用生成的 client_id 和 client_secret 请求获取token接口 http://localhost:5700/open/auth/token?client_id=xxxxxx&client_secret=xxxxxxxx
String responseStr = HttpRequest.get(GET_TOKEN+"?client_id="+CLIENT_ID+"&client_secret="+CLIENT_SECRET)
.execute().body();
if (ObjectUtil.isNotEmpty(responseStr)) {
//{"code":200,"data":{"token":"950e3060-d714-4f6a-9839-c098a116f0a8","token_type":"Bearer","expiration":1705743778}}
JSONObject jsonObject = JSON.parseObject(responseStr);
System.out.println(jsonObject.getString("code"));
if (Objects.equals(String.valueOf(jsonObject.getString("code")), "200")) {
JSONObject response = jsonObject.getJSONObject("data");
redisCache.setCacheObject(QL_TOKEN_KEY, (String)response.get("token"), (int)response.get("expiration"), TimeUnit.SECONDS);
return (String) response.get("token");
}
}
sleep(500);
}
return null;
}
}
}

View File

@@ -0,0 +1,25 @@
server:
port: 5700
spring:
application:
name: MT2QL
#数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://134.175.126.60:43306/MT2QL?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8
username: root
password: mysql_xWbMcG
#redis配置
redis:
host: 134.175.126.60
port: 46379
database: 0
timeout: 1800000
lettuce:
pool:
max-active: 20
#最大阻塞等待时间(负数表示没限制)
max-wait: -1
max-idle: 5
min-idle: 0
password: jhkdjhkjdhsIUTYURTU_HQw7tC # 文件上传

View File

@@ -0,0 +1,22 @@
server:
port: 18080
spring:
application:
name: MT2QL
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.31.88:3306/MT2QL?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8
username: root
password: mysql_xWbMcG
redis:
host: 192.168.31.88
port: 6379
database: 2
timeout: 1800000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5
min-idle: 0
password: jhkdjhkjdhsIUTYURTU_HQw7tC

View File

@@ -0,0 +1,5 @@
spring:
application:
name: MT2QL
profiles:
active: dev

View File

@@ -0,0 +1,6 @@
<html>
<body>
<h1>hello word!!!</h1>
<p>this is a html page</p>
</body>
</html>

View File

@@ -0,0 +1,13 @@
package cn.van333.mt2ql;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Mt2QlApplicationTests {
@Test
void contextLoads() {
}
}