mirror of
https://github.com/ttttupup/wxhelper.git
synced 2024-11-22 18:29:23 +08:00
java客户端
This commit is contained in:
parent
81ea48b534
commit
3f66d7b6d0
33
java_client/.gitignore
vendored
Normal file
33
java_client/.gitignore
vendored
Normal 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/
|
15
java_client/README.md
Normal file
15
java_client/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
环境为jdk17
|
||||||
|
执行之后会在当前项目所处磁盘根路径生成一个exec文件夹,然后会把src/main/resources/exec下的文件放在那避免因为路径问题出错
|
||||||
|
|
||||||
|
项目启动之后,会生成一个tcp服务端,用来接受hook信息,然后把接收的信息放在队列中,之后用一个线程去循环处理消息.
|
||||||
|
具体实现可以看
|
||||||
|
```com.example.wxhk.tcp.vertx```包下的三个文件
|
||||||
|
|
||||||
|
com.example.wxhk.tcp.vertx.VertxTcp 这个是tcp服务端,接受信息
|
||||||
|
|
||||||
|
com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化
|
||||||
|
|
||||||
|
com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理
|
||||||
|
|
||||||
|
|
||||||
|
启动项目需要去修改配置文件的微信路径
|
184
java_client/pom.xml
Normal file
184
java_client/pom.xml
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<?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>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>com.example</groupId>
|
||||||
|
<artifactId>wxhk</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>wxhk</name>
|
||||||
|
<description>wxhk</description>
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
|
<!-- <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
|
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.92.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>4.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-core</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-web</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-web-client</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-mysql-client</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>6.0.0.M3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.15.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<!-- 排除文件配置 -->
|
||||||
|
<!-- <excludes> -->
|
||||||
|
<!-- <exclude>*.**</exclude> -->
|
||||||
|
<!-- <exclude>*/**.xml</exclude> -->
|
||||||
|
<!-- </excludes> -->
|
||||||
|
|
||||||
|
<!-- 包含文件配置,现在只打包 com 文件夹 -->
|
||||||
|
<includes>
|
||||||
|
<include>
|
||||||
|
**/com/example/wxhk/**
|
||||||
|
</include>
|
||||||
|
</includes>
|
||||||
|
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<!-- 配置加入依赖包 -->
|
||||||
|
<addClasspath>true</addClasspath>
|
||||||
|
<classpathPrefix>lib/</classpathPrefix>
|
||||||
|
<useUniqueVersions>false</useUniqueVersions>
|
||||||
|
<!-- Spring Boot 启动类(自行修改) -->
|
||||||
|
<mainClass>com.example.wxhk.WxhkApplication</mainClass>
|
||||||
|
</manifest>
|
||||||
|
<manifestEntries>
|
||||||
|
<!-- 外部资源路径加入 manifest.mf 的 Class-Path -->
|
||||||
|
<Class-Path>resources/</Class-Path>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
<!-- jar 输出目录 -->
|
||||||
|
<outputDirectory>${project.build.directory}/pack/</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<!-- 复制依赖 -->
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-dependencies</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- 依赖包 输出目录 -->
|
||||||
|
<outputDirectory>${project.build.directory}/pack/lib</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<!-- 复制资源 -->
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-resources</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<!-- 资源文件 输出目录 -->
|
||||||
|
<outputDirectory>${project.build.directory}/pack/resources</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.example.wxhk;
|
||||||
|
|
||||||
|
import io.vertx.core.Vertx;
|
||||||
|
import io.vertx.core.VertxOptions;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class WxhkApplication {
|
||||||
|
public static final Vertx vertx;
|
||||||
|
|
||||||
|
static {
|
||||||
|
vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(5).setEventLoopPoolSize(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
//ConsoleInject.exe -i WeChat.exe -p D:\wxhelper.dll
|
||||||
|
//ConsoleApplication.exe -I 4568 -p C:\wxhelper.dll -m 17484 -P 1888
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(WxhkApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.example.wxhk.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接受到的微信消息类型
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/26
|
||||||
|
*/
|
||||||
|
public enum WxMsgType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
私聊信息(1),
|
||||||
|
好友请求(37),
|
||||||
|
收到名片(42),
|
||||||
|
表情(47),
|
||||||
|
转账和收款(49),
|
||||||
|
收到转账之后(51),
|
||||||
|
/**
|
||||||
|
* 扫码触发,会触发2次, 有一次有编号,一次没有,还有登陆之后也有,很多情况都会调用这个
|
||||||
|
*/
|
||||||
|
扫码触发(10002),
|
||||||
|
|
||||||
|
;
|
||||||
|
Integer type;
|
||||||
|
|
||||||
|
public Integer getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
WxMsgType(Integer type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.wxhk.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
|
||||||
|
public class WxMsgController {
|
||||||
|
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
|
||||||
|
|
||||||
|
void init(){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.example.wxhk.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私聊
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/26
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class PrivateChatMsg implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容
|
||||||
|
*/
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* 当是群聊的时候 为群id,否则为微信id
|
||||||
|
*/
|
||||||
|
private String fromGroup;
|
||||||
|
/**
|
||||||
|
* 微信id
|
||||||
|
*/
|
||||||
|
private String fromUser;
|
||||||
|
private Integer isSendMsg;
|
||||||
|
/**
|
||||||
|
* 1通过手机发送
|
||||||
|
*/
|
||||||
|
private Integer isSendByPhone;
|
||||||
|
private Long msgId;
|
||||||
|
private Integer pid;
|
||||||
|
private String sign;
|
||||||
|
private String signature;
|
||||||
|
private String time;
|
||||||
|
private Integer timestamp;
|
||||||
|
|
||||||
|
String path;
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
}
|
214
java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java
Normal file
214
java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package com.example.wxhk.msg;
|
||||||
|
|
||||||
|
import com.example.wxhk.constant.WxMsgType;
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import com.example.wxhk.util.HttpAsyncUtil;
|
||||||
|
import com.example.wxhk.util.HttpSendUtil;
|
||||||
|
import com.example.wxhk.util.HttpSyncUtil;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.dromara.hutool.core.util.XmlUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WxMsgHandle {
|
||||||
|
public static final ConcurrentHashMap<Integer, Handle> map = new ConcurrentHashMap<>(32);
|
||||||
|
|
||||||
|
public static ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
|
||||||
|
protected static final Log log=Log.get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public WxMsgHandle() {
|
||||||
|
add(chatMsg -> {
|
||||||
|
return 1;
|
||||||
|
},WxMsgType.私聊信息);// 好友请求
|
||||||
|
add(chatMsg -> {
|
||||||
|
HttpSendUtil.通过好友请求(chatMsg);
|
||||||
|
return 1;
|
||||||
|
},WxMsgType.好友请求);// 好友请求
|
||||||
|
add(chatMsg -> {
|
||||||
|
boolean f = 解析扫码支付第二段(chatMsg);
|
||||||
|
if(f){
|
||||||
|
f=解析收款信息1段(chatMsg);
|
||||||
|
if(f){
|
||||||
|
解析收款信息2段(chatMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},WxMsgType.转账和收款);
|
||||||
|
add(chatMsg -> {
|
||||||
|
boolean f = 解析扫码支付第一段(chatMsg);
|
||||||
|
return null;
|
||||||
|
},WxMsgType.扫码触发);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析扫码支付第一段,得到交易id和微信id
|
||||||
|
*
|
||||||
|
* @param chatMsg
|
||||||
|
* @return boolean 返回true 则继续解析,否则解析成功,不需要解析了
|
||||||
|
*/
|
||||||
|
public static boolean 解析扫码支付第一段(PrivateChatMsg chatMsg) {
|
||||||
|
try {
|
||||||
|
Document document = XmlUtil.parseXml(chatMsg.getContent());
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
String localName = documentElement.getLocalName();
|
||||||
|
if("sysmsg".equals(localName)){
|
||||||
|
String type = documentElement.getAttribute("type");
|
||||||
|
if("paymsg".equals(type)){
|
||||||
|
NodeList outtradeno = documentElement.getElementsByTagName("outtradeno");
|
||||||
|
if (outtradeno.getLength()>0) {
|
||||||
|
String textContent = outtradeno.item(0).getTextContent();
|
||||||
|
String textContent1 = documentElement.getElementsByTagName("username").item(0).getTextContent();
|
||||||
|
cache.put(textContent,textContent1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析扫码支付第二段
|
||||||
|
*
|
||||||
|
* @param chatMsg 聊天味精
|
||||||
|
* @return boolean true 则 继续解析, false则解析成功,不需要再解析了
|
||||||
|
*/
|
||||||
|
public static boolean 解析扫码支付第二段(PrivateChatMsg chatMsg) {
|
||||||
|
try {
|
||||||
|
Document document = XmlUtil.parseXml(chatMsg.getContent());
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
String localName = documentElement.getLocalName();
|
||||||
|
if("msg".equals(localName)){
|
||||||
|
NodeList outtradeno = documentElement.getElementsByTagName("weapp_path");
|
||||||
|
if(outtradeno.getLength()>1){
|
||||||
|
String textContent = outtradeno.item(1).getTextContent();
|
||||||
|
Set<Map.Entry<String, String>> entries = cache.entrySet();
|
||||||
|
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
|
||||||
|
while (iterator.hasNext()){
|
||||||
|
Map.Entry<String, String> next = iterator.next();
|
||||||
|
if (textContent.contains(next.getKey())) {
|
||||||
|
// 得到了交易信息
|
||||||
|
NodeList word = documentElement.getElementsByTagName("word");
|
||||||
|
String monery = word.item(1).getTextContent();
|
||||||
|
String remark = word.item(3).getTextContent();
|
||||||
|
if(monery.startsWith("¥")){
|
||||||
|
String substring = monery.substring(1);
|
||||||
|
BigDecimal decimal = new BigDecimal(substring);
|
||||||
|
log.info("扫码收款:{},付款人:{},付款备注:{}",decimal.stripTrailingZeros().toPlainString(),next.getValue(),remark);
|
||||||
|
iterator.remove();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public static boolean 解析收款信息2段(PrivateChatMsg chatMsg) {
|
||||||
|
try {
|
||||||
|
Document document = XmlUtil.parseXml(chatMsg.getContent());
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
String localName = documentElement.getLocalName();
|
||||||
|
if("msg".equals(localName)){
|
||||||
|
if (documentElement.getElementsByTagName("transcationid").getLength()>0) {
|
||||||
|
String remark = documentElement.getElementsByTagName("pay_memo").item(0).getTextContent();
|
||||||
|
String monery = documentElement.getElementsByTagName("feedesc").item(0).getTextContent();
|
||||||
|
String receiver_username = documentElement.getElementsByTagName("receiver_username").item(0).getTextContent();
|
||||||
|
if(InitWeChat.WXID_MAP.contains(receiver_username)){
|
||||||
|
// 如果是自己转出去的,则不需要解析了
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(monery.startsWith("¥")){
|
||||||
|
String substring = monery.substring(1);
|
||||||
|
BigDecimal decimal = new BigDecimal(substring);
|
||||||
|
log.info("收款:{},付款人:{},付款备注:{}",decimal.stripTrailingZeros().toPlainString(),receiver_username,remark);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析收款信息1段
|
||||||
|
* <b>会自动进行收款</b>
|
||||||
|
* @param chatMsg
|
||||||
|
* @return boolean true则 继续解析,false则不需要解析了
|
||||||
|
*/
|
||||||
|
public static boolean 解析收款信息1段(PrivateChatMsg chatMsg){
|
||||||
|
try {
|
||||||
|
String content = chatMsg.getContent();
|
||||||
|
Document document = XmlUtil.parseXml(content);
|
||||||
|
Node paysubtype = document.getElementsByTagName("paysubtype").item(0);
|
||||||
|
if("1".equals(paysubtype.getTextContent().trim())){
|
||||||
|
// 手机发出去的
|
||||||
|
String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent();
|
||||||
|
if(!InitWeChat.WXID_MAP.contains(textContent)){
|
||||||
|
// 如果不是机器人收款,则认为不需要解析了,大概率是机器人自己发出去的
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0);
|
||||||
|
Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0);
|
||||||
|
HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid",chatMsg.getFromUser())
|
||||||
|
.put("transcationId",transcationid.getTextContent())
|
||||||
|
.put("transferId",transferid.getTextContent()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface Handle{
|
||||||
|
Object handle(PrivateChatMsg chatMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void add(Handle handle, WxMsgType...type){
|
||||||
|
for (WxMsgType integer : type) {
|
||||||
|
map.put(integer.getType(), handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exec(PrivateChatMsg chatMsg){
|
||||||
|
Handle handle = map.get(chatMsg.getType());
|
||||||
|
if (handle != null) {
|
||||||
|
handle.handle(chatMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package com.example.wxhk.tcp.vertx;
|
||||||
|
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.msg.WxMsgHandle;
|
||||||
|
import com.example.wxhk.util.HttpSendUtil;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.dromara.hutool.core.thread.NamedThreadFactory;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息处理
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/31
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ArrHandle {
|
||||||
|
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(1, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false));
|
||||||
|
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void exec(){
|
||||||
|
for (int i = 0; i < sub.getCorePoolSize(); i++) {
|
||||||
|
sub.submit(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()){
|
||||||
|
try {
|
||||||
|
JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE.take();
|
||||||
|
log.info("{}",take.encode());
|
||||||
|
|
||||||
|
PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class);
|
||||||
|
if("weixin".equals(privateChatMsg.getFromUser())){
|
||||||
|
String s = HttpSendUtil.获取当前登陆微信id();
|
||||||
|
InitWeChat.WXID_MAP.add(s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
WxMsgHandle.exec(privateChatMsg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.error("退出线程了");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,163 @@
|
|||||||
|
package com.example.wxhk.tcp.vertx;
|
||||||
|
|
||||||
|
import com.example.wxhk.util.HttpAsyncUtil;
|
||||||
|
import com.example.wxhk.util.HttpSyncUtil;
|
||||||
|
import io.vertx.core.impl.ConcurrentHashSet;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.dromara.hutool.core.io.file.FileUtil;
|
||||||
|
import org.dromara.hutool.core.net.NetUtil;
|
||||||
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.dromara.hutool.setting.Setting;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信注入环境初始化和相关方法
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/16
|
||||||
|
*/
|
||||||
|
@Order(-1)
|
||||||
|
@Component
|
||||||
|
public class InitWeChat implements CommandLineRunner {
|
||||||
|
|
||||||
|
public final static Log log = Log.get();
|
||||||
|
|
||||||
|
public static String wxPath;
|
||||||
|
|
||||||
|
public static Integer wxPort;
|
||||||
|
public static Integer vertxPort;
|
||||||
|
/**
|
||||||
|
* wxhelper.dll 所在路径
|
||||||
|
*/
|
||||||
|
public static File DLL_PATH;
|
||||||
|
|
||||||
|
public static final ConcurrentHashSet<String> WXID_MAP=new ConcurrentHashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
public static void 注入dll(String wxPid) throws IOException {
|
||||||
|
String format = StrUtil.format("cmd /C c.exe -I {} -p {}\\wxhelper.dll -m {}", wxPid, DLL_PATH.getAbsolutePath(), wxPid);
|
||||||
|
Process exec = Runtime.getRuntime().exec(format, null, DLL_PATH);
|
||||||
|
log.info("注入结果:{}", new String(exec.getInputStream().readAllBytes(), "gbk"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static File 环境初始化() {
|
||||||
|
File target = new File(new File("").getAbsolutePath().split("\\\\")[0] + "\\exec\\");
|
||||||
|
try {
|
||||||
|
File wxPathFile = new File(wxPath);
|
||||||
|
File config = new File(wxPathFile.getParentFile(), "config.ini");
|
||||||
|
Setting setting = new Setting(config.getAbsolutePath());
|
||||||
|
setting.getGroupedMap().put("config", "port", String.valueOf(wxPort));
|
||||||
|
setting.store();
|
||||||
|
ClassPathResource classPathResource = new ClassPathResource("exec");
|
||||||
|
File file = classPathResource.getFile();
|
||||||
|
target.mkdir();
|
||||||
|
for (File listFile : file.listFiles()) {
|
||||||
|
FileUtil.copy(listFile, target, true);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e, "环境初始化失败,请检查");
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回最后一个微信的pid
|
||||||
|
*
|
||||||
|
* @return {@link String}
|
||||||
|
* @throws IOException ioexception
|
||||||
|
*/
|
||||||
|
public static String createWx() throws IOException {
|
||||||
|
Runtime.getRuntime().exec("cmd /C \"" + wxPath + "\"");
|
||||||
|
return getWxPid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static String getWxPid() throws IOException {
|
||||||
|
String line = null;
|
||||||
|
try {
|
||||||
|
Process exec = Runtime.getRuntime().exec("cmd /C tasklist /FI \"IMAGENAME eq WeChat.exe\" ");
|
||||||
|
byte[] bytes = exec.getInputStream().readAllBytes();
|
||||||
|
line = new String(bytes, "gbk");
|
||||||
|
String[] split = line.split("\n");
|
||||||
|
if (!line.contains("WeChat.exe")) {
|
||||||
|
return createWx();
|
||||||
|
}
|
||||||
|
String[] split1 = split[split.length - 1].replaceAll("\\s{2,}", " ").split(" ");
|
||||||
|
return split1[1];
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("获取端口错误:{}", line);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getWxPort() {
|
||||||
|
return wxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${wx.port}")
|
||||||
|
public void setWxPort(Integer wxPort) {
|
||||||
|
InitWeChat.wxPort = wxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getWxPath() {
|
||||||
|
return wxPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${wx.path}")
|
||||||
|
public void setWxPath(String wxPath) {
|
||||||
|
InitWeChat.wxPath = wxPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getVertxPort() {
|
||||||
|
return vertxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${vertx.port}")
|
||||||
|
public void setVertxPort(Integer vertxPort) {
|
||||||
|
InitWeChat.vertxPort = vertxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
//tasklist /FI "IMAGENAME eq WeChat.exe" /m
|
||||||
|
boolean usableLocalPort = NetUtil.isUsableLocalPort(wxPort);
|
||||||
|
if (usableLocalPort) {
|
||||||
|
DLL_PATH = 环境初始化();
|
||||||
|
String wxPid = getWxPid();
|
||||||
|
注入dll(wxPid);
|
||||||
|
}
|
||||||
|
ThreadUtil.execute(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()){
|
||||||
|
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.检查微信登陆, new JsonObject());
|
||||||
|
if(exec.getInteger("code").equals(1)){
|
||||||
|
JsonObject dl = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject());
|
||||||
|
JsonObject jsonObject = dl.getJsonObject("data");
|
||||||
|
String wx = jsonObject.getString("wxid");
|
||||||
|
WXID_MAP.add(wx);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("检测到微信登陆:{}",wx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ThreadUtil.safeSleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
HttpSyncUtil.exec(HttpAsyncUtil.Type.关闭hook, new JsonObject());
|
||||||
|
}));
|
||||||
|
//netstat -aon|findstr "端口号"
|
||||||
|
// c.exe -I 4568 -p D:\exec\wxhelper.dll -m 4568
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package com.example.wxhk.tcp.vertx;
|
||||||
|
|
||||||
|
import com.example.wxhk.WxhkApplication;
|
||||||
|
import com.example.wxhk.util.HttpAsyncUtil;
|
||||||
|
import io.vertx.core.AbstractVerticle;
|
||||||
|
import io.vertx.core.DeploymentOptions;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.core.net.NetServer;
|
||||||
|
import io.vertx.core.net.NetServerOptions;
|
||||||
|
import io.vertx.core.parsetools.JsonParser;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接受微信hook信息
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/26
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order()
|
||||||
|
public class VertxTcp extends AbstractVerticle implements CommandLineRunner {
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
NetServer netServer;
|
||||||
|
public final static LinkedBlockingQueue<JsonObject> LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Promise<Void> startPromise) throws Exception {
|
||||||
|
netServer = vertx.createNetServer(new NetServerOptions()
|
||||||
|
.setPort(InitWeChat.getVertxPort())
|
||||||
|
.setIdleTimeout(0)
|
||||||
|
.setLogActivity(false)
|
||||||
|
);
|
||||||
|
netServer.connectHandler(socket -> {
|
||||||
|
JsonParser parser = JsonParser.newParser();
|
||||||
|
parser.objectValueMode();
|
||||||
|
parser.handler(event -> {
|
||||||
|
switch (event.type()) {
|
||||||
|
case START_OBJECT -> {
|
||||||
|
}
|
||||||
|
case END_OBJECT -> {
|
||||||
|
}
|
||||||
|
case START_ARRAY -> {
|
||||||
|
}
|
||||||
|
case END_ARRAY -> {
|
||||||
|
}
|
||||||
|
case VALUE -> {
|
||||||
|
LINKED_BLOCKING_QUEUE.add(event.objectValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.handler(parser);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<NetServer> listen = netServer.listen();
|
||||||
|
listen.onComplete(event -> {
|
||||||
|
boolean succeeded = event.succeeded();
|
||||||
|
if (succeeded) {
|
||||||
|
HttpAsyncUtil.exec(HttpAsyncUtil.Type.开启hook, new JsonObject().put("port", "8080").put("ip", "127.0.0.1"));
|
||||||
|
startPromise.complete();
|
||||||
|
}else{
|
||||||
|
startPromise.fail(event.cause());
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
WxhkApplication.vertx.deployVerticle(this,new DeploymentOptions().setWorkerPoolSize(6));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package com.example.wxhk.util;
|
||||||
|
|
||||||
|
import com.example.wxhk.WxhkApplication;
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import io.vertx.core.AsyncResult;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.client.WebClientOptions;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http异步请求
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/25
|
||||||
|
*/
|
||||||
|
public class HttpAsyncUtil {
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
public static final WebClient client = WebClient.create(WxhkApplication.vertx,new WebClientOptions().setDefaultHost("localhost").setDefaultPort(InitWeChat.wxPort)
|
||||||
|
.setConnectTimeout(10000).setMaxPoolSize(10).setPoolEventLoopSize(10));
|
||||||
|
|
||||||
|
public static Future<HttpResponse<Buffer>> exec(Type type, JsonObject object) {
|
||||||
|
return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType())
|
||||||
|
.sendJsonObject(object)
|
||||||
|
.onSuccess(event ->
|
||||||
|
{
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("type:{},{}",type.getType(), event.bodyAsJsonObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Future<HttpResponse<Buffer>> exec(Type type, JsonObject object, Handler<AsyncResult<HttpResponse<Buffer>>> handler) {
|
||||||
|
return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType())
|
||||||
|
.sendJsonObject(object)
|
||||||
|
.onComplete(handler)
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
检查微信登陆("0"),
|
||||||
|
获取登录信息("1"),
|
||||||
|
发送文本("2"),
|
||||||
|
发送at文本("3"),
|
||||||
|
发送图片("5"),
|
||||||
|
发送文件("6"),
|
||||||
|
开启hook("9"),
|
||||||
|
关闭hook("10"),
|
||||||
|
通过好友申请("23"),
|
||||||
|
获取群成员("25"),
|
||||||
|
获取群成员昵称("26"),
|
||||||
|
删除群成员("27"),
|
||||||
|
确认收款("45"),
|
||||||
|
联系人列表("46"),
|
||||||
|
查询微信信息("55"),
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
String type;
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.example.wxhk.util;
|
||||||
|
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.dromara.hutool.core.util.XmlUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常见方法
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/29
|
||||||
|
*/
|
||||||
|
public class HttpSendUtil {
|
||||||
|
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
public static JsonObject 通过好友请求(PrivateChatMsg msg){
|
||||||
|
Document document = XmlUtil.parseXml(msg.getContent());
|
||||||
|
String encryptusername = document.getDocumentElement().getAttribute("encryptusername");
|
||||||
|
String ticket = document.getDocumentElement().getAttribute("ticket");
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.通过好友申请, new JsonObject().put("v3", encryptusername).put("v4", ticket).put("permission", "0"));
|
||||||
|
}
|
||||||
|
public static JsonObject 确认收款(PrivateChatMsg msg){
|
||||||
|
try {
|
||||||
|
String content = msg.getContent();
|
||||||
|
Document document = XmlUtil.parseXml(content);
|
||||||
|
Node paysubtype = document.getElementsByTagName("paysubtype").item(0);
|
||||||
|
if("1".equals(paysubtype.getTextContent().trim())){
|
||||||
|
// 手机发出去的
|
||||||
|
String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent();
|
||||||
|
if(!InitWeChat.WXID_MAP.contains(textContent)){
|
||||||
|
return new JsonObject().put("spick",true);
|
||||||
|
}
|
||||||
|
Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0);
|
||||||
|
Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0);
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid",msg.getFromUser())
|
||||||
|
.put("transcationId",transcationid.getTextContent())
|
||||||
|
.put("transferId",transferid.getTextContent()));
|
||||||
|
|
||||||
|
}
|
||||||
|
// 如果是确认接受收款,则跳过
|
||||||
|
return new JsonObject();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String 获取当前登陆微信id(){
|
||||||
|
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject());
|
||||||
|
return exec.getJsonObject("data").getString("wxid");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.example.wxhk.util;
|
||||||
|
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.dromara.hutool.http.client.ClientConfig;
|
||||||
|
import org.dromara.hutool.http.client.Request;
|
||||||
|
import org.dromara.hutool.http.client.engine.ClientEngine;
|
||||||
|
import org.dromara.hutool.http.client.engine.ClientEngineFactory;
|
||||||
|
import org.dromara.hutool.http.meta.Method;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http同步请求
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/25
|
||||||
|
*/
|
||||||
|
public class HttpSyncUtil {
|
||||||
|
static final ClientEngine engine;
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
static {
|
||||||
|
ClientConfig clientConfig = ClientConfig.of()
|
||||||
|
.setTimeout(30 * 1000);
|
||||||
|
engine = ClientEngineFactory.createEngine(clientConfig);
|
||||||
|
|
||||||
|
}
|
||||||
|
public static JsonObject exec(HttpAsyncUtil.Type type,JsonObject obj){
|
||||||
|
String post = engine.send(Request.of("http://localhost:" + InitWeChat.wxPort + "/api/?type=" + type.getType()).method(Method.POST).body(obj.encode())).bodyStr();
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("type:{},{}",type.getType(),post);
|
||||||
|
}
|
||||||
|
return new JsonObject(post);
|
||||||
|
}
|
||||||
|
}
|
4
java_client/src/main/resources/application.properties
Normal file
4
java_client/src/main/resources/application.properties
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
wx.path=D:\\Program Files (x86)\\Tencent\\WeChat\\[3.9.2.23]\\WeChat.exe
|
||||||
|
wx.port=19088
|
||||||
|
spring.profiles.active=local
|
||||||
|
vertx.port=8080
|
BIN
java_client/src/main/resources/exec/c.exe
Normal file
BIN
java_client/src/main/resources/exec/c.exe
Normal file
Binary file not shown.
BIN
java_client/src/main/resources/exec/wxhelper.dll
Normal file
BIN
java_client/src/main/resources/exec/wxhelper.dll
Normal file
Binary file not shown.
171
java_client/src/main/resources/logback-spring.xml
Normal file
171
java_client/src/main/resources/logback-spring.xml
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<!--日志格式应用spring boot默认的格式,也可以自己更改-->
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
|
||||||
|
<!--定义日志存放的位置,默认存放在项目启动的相对路径的目录-->
|
||||||
|
<springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="log"/>
|
||||||
|
<property name="withLineNumber_debug"
|
||||||
|
value="%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) [%t] %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
|
||||||
|
|
||||||
|
<property name="file_pattern"
|
||||||
|
value="%d{MM-dd HH:mm:ss.SSS} %-5level [${PID:- } %thread] %logger{50}#%method,%line : %msg%n"/>
|
||||||
|
|
||||||
|
<!-- ****************************************************************************************** -->
|
||||||
|
<!-- ****************************** 本地开发只在控制台打印日志 ************************************ -->
|
||||||
|
<!-- ****************************************************************************************** -->
|
||||||
|
<springProfile name="local">
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${withLineNumber_debug}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 日志记录器,日期滚动记录 -->
|
||||||
|
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>log_error.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<!-- 此日志文件只记录error级别的 -->
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>error</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!--默认所有的包以info-->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!--各个服务的包在本地执行的时候,打开debug模式-->
|
||||||
|
<logger name="com.example.wxhk" level="debug" additivity="false">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</logger>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- ********************************************************************************************** -->
|
||||||
|
<!-- **** 放到服务器上不管在什么环境都只在文件记录日志,控制台(catalina.out)打印logback捕获不到的日志 **** -->
|
||||||
|
<!-- ********************************************************************************************** -->
|
||||||
|
<springProfile name="!local">
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 日志记录器,日期滚动记录 -->
|
||||||
|
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>${LOG_PATH}/log_error.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<!-- 此日志文件只记录error级别的 -->
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>error</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 日志记录器,日期滚动记录 -->
|
||||||
|
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>${LOG_PATH}/log_total.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/total/%d{yyyy-MM}/log_total-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<!-- 业务错误 -->
|
||||||
|
<appender name="business_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>${LOG_PATH}/log_business.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/business/%d{yyyy-MM}/log_business-%d{yyyy-MM-dd}.%i.log.gz
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.example.wxhk" level="info" additivity="false">
|
||||||
|
<appender-ref ref="business_log"/>
|
||||||
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="p6spy" level="info" additivity="false">
|
||||||
|
<appender-ref ref="business_log"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="org.springframework" level="warn"/>
|
||||||
|
|
||||||
|
<!--记录到文件时,记录两类一类是error日志,一个是所有日志-->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
|
<appender-ref ref="FILE_ALL"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
|
10
java_client/src/main/resources/spy.properties
Normal file
10
java_client/src/main/resources/spy.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
|
||||||
|
# 使用Slf4J记录sql
|
||||||
|
appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||||
|
# 是否开启慢SQL记录
|
||||||
|
outagedetection=true
|
||||||
|
# 慢SQL记录标准,单位秒
|
||||||
|
outagedetectioninterval=2
|
||||||
|
#日期格式
|
||||||
|
dateformat=HH:mm:ss
|
||||||
|
customLogMessageFormat=%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine)
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.example.wxhk;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class WxhkApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.example.wxhk.tcp;
|
||||||
|
|
||||||
|
import com.example.wxhk.util.HttpAsyncUtil;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
import org.dromara.hutool.core.lang.Console;
|
||||||
|
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class HttpAsyncUtilTest {
|
||||||
|
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exec() {
|
||||||
|
Future<HttpResponse<Buffer>> exec = HttpAsyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject());
|
||||||
|
exec.onSuccess(event -> {
|
||||||
|
Console.log(event.bodyAsJsonObject());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void exec1() {
|
||||||
|
|
||||||
|
for(int i=0;i<10000;i++){
|
||||||
|
int finalI = i;
|
||||||
|
HttpAsyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject(), event -> {
|
||||||
|
if (event.succeeded()) {
|
||||||
|
log.info("i:{},{}", finalI,event.result().bodyAsJsonObject());
|
||||||
|
}else{
|
||||||
|
event.cause().printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log.info("发出请求:{}",i);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadUtil.sync(this);
|
||||||
|
}
|
||||||
|
}
|
72
java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java
Normal file
72
java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user