diff --git a/java_client/.gitignore b/java_client/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/java_client/.gitignore
@@ -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/
diff --git a/java_client/README.md b/java_client/README.md
new file mode 100644
index 0000000..9c712ab
--- /dev/null
+++ b/java_client/README.md
@@ -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 循环消息处理
+
+
+启动项目需要去修改配置文件的微信路径
diff --git a/java_client/pom.xml b/java_client/pom.xml
new file mode 100644
index 0000000..3a98e6f
--- /dev/null
+++ b/java_client/pom.xml
@@ -0,0 +1,184 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.0
+
+
+ com.example
+ wxhk
+ 0.0.1-SNAPSHOT
+ wxhk
+ wxhk
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+
+
+
+
+
+ io.netty
+ netty-all
+ 4.1.92.Final
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.11.0
+
+
+
+ io.vertx
+ vertx-core
+ 4.4.2
+
+
+ io.vertx
+ vertx-web
+ 4.4.2
+
+
+ io.vertx
+ vertx-web-client
+ 4.4.2
+
+
+ io.vertx
+ vertx-mysql-client
+ 4.4.2
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.dromara.hutool
+ hutool-all
+ 6.0.0.M3
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.15.1
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+
+
+
+
+
+
+
+
+ **/com/example/wxhk/**
+
+
+
+
+
+
+ true
+ lib/
+ false
+
+ com.example.wxhk.WxhkApplication
+
+
+
+ resources/
+
+
+
+ ${project.build.directory}/pack/
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+
+ ${project.build.directory}/pack/lib
+
+
+
+
+
+ maven-resources-plugin
+
+
+
+ copy-resources
+ package
+
+ copy-resources
+
+
+
+
+ src/main/resources
+
+
+
+ ${project.build.directory}/pack/resources
+
+
+
+
+
+
+
+
diff --git a/java_client/src/main/java/com/example/wxhk/WxhkApplication.java b/java_client/src/main/java/com/example/wxhk/WxhkApplication.java
new file mode 100644
index 0000000..da0ccbd
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/WxhkApplication.java
@@ -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);
+ }
+
+}
diff --git a/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java
new file mode 100644
index 0000000..59b9570
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java
@@ -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;
+ }
+}
diff --git a/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java
new file mode 100644
index 0000000..05e4ede
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java
@@ -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(){
+
+ }
+}
diff --git a/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java
new file mode 100644
index 0000000..ceb6da0
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java
@@ -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;
+}
diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java
new file mode 100644
index 0000000..a31fec0
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java
@@ -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 map = new ConcurrentHashMap<>(32);
+
+ public static ConcurrentHashMap 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> entries = cache.entrySet();
+ Iterator> iterator = entries.iterator();
+ while (iterator.hasNext()){
+ Map.Entry 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段
+ * 会自动进行收款
+ * @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);
+ }
+ }
+}
diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java
new file mode 100644
index 0000000..39add74
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java
@@ -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("退出线程了");
+ });
+ }
+
+ }
+
+}
diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java
new file mode 100644
index 0000000..649eaf3
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java
@@ -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 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
+ }
+}
diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java
new file mode 100644
index 0000000..aad3656
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java
@@ -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 LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>();
+
+
+
+ @Override
+ public void start(Promise 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 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));
+ }
+}
diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java
new file mode 100644
index 0000000..49d9015
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java
@@ -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> 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> exec(Type type, JsonObject object, Handler>> 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;
+ }
+ }
+}
diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java
new file mode 100644
index 0000000..8a815ea
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java
@@ -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");
+ }
+
+}
diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java
new file mode 100644
index 0000000..d5a2a63
--- /dev/null
+++ b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java
@@ -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);
+ }
+}
diff --git a/java_client/src/main/resources/application.properties b/java_client/src/main/resources/application.properties
new file mode 100644
index 0000000..199c37a
--- /dev/null
+++ b/java_client/src/main/resources/application.properties
@@ -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
\ No newline at end of file
diff --git a/java_client/src/main/resources/exec/c.exe b/java_client/src/main/resources/exec/c.exe
new file mode 100644
index 0000000..68787d2
Binary files /dev/null and b/java_client/src/main/resources/exec/c.exe differ
diff --git a/java_client/src/main/resources/exec/wxhelper.dll b/java_client/src/main/resources/exec/wxhelper.dll
new file mode 100644
index 0000000..168a32b
Binary files /dev/null and b/java_client/src/main/resources/exec/wxhelper.dll differ
diff --git a/java_client/src/main/resources/logback-spring.xml b/java_client/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000..5027603
--- /dev/null
+++ b/java_client/src/main/resources/logback-spring.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${withLineNumber_debug}
+ utf-8
+
+
+
+
+
+
+
+ log_error.log
+
+
+ ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz
+ 50MB
+ 100
+
+
+
+ true
+
+
+
+ ${file_pattern}
+ utf-8
+
+
+
+
+ error
+ ACCEPT
+ DENY
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${CONSOLE_LOG_PATTERN}
+ utf-8
+
+
+
+
+
+
+
+ ${LOG_PATH}/log_error.log
+
+
+ ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz
+ 50MB
+ 100
+
+
+
+ true
+
+
+
+ ${file_pattern}
+ utf-8
+
+
+
+
+ error
+ ACCEPT
+ DENY
+
+
+
+
+
+
+
+ ${LOG_PATH}/log_total.log
+
+
+ ${LOG_PATH}/total/%d{yyyy-MM}/log_total-%d{yyyy-MM-dd}.%i.log.gz
+ 50MB
+ 100
+
+
+ true
+
+
+ ${file_pattern}
+ utf-8
+
+
+
+
+
+ ${LOG_PATH}/log_business.log
+
+
+ ${LOG_PATH}/business/%d{yyyy-MM}/log_business-%d{yyyy-MM-dd}.%i.log.gz
+
+ 50MB
+ 100
+
+
+ true
+
+
+ ${file_pattern}
+ utf-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_client/src/main/resources/spy.properties b/java_client/src/main/resources/spy.properties
new file mode 100644
index 0000000..d64f7b1
--- /dev/null
+++ b/java_client/src/main/resources/spy.properties
@@ -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)
\ No newline at end of file
diff --git a/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java b/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java
new file mode 100644
index 0000000..c7a768f
--- /dev/null
+++ b/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java
@@ -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() {
+ }
+
+}
diff --git a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java
new file mode 100644
index 0000000..6bf1ddf
--- /dev/null
+++ b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java
@@ -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> 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);
+ }
+}
\ No newline at end of file
diff --git a/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java
new file mode 100644
index 0000000..e4bf159
--- /dev/null
+++ b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java
@@ -0,0 +1,72 @@
+package com.example.wxhk.tcp;
+
+import com.example.wxhk.model.PrivateChatMsg;
+import com.example.wxhk.msg.WxMsgHandle;
+import io.vertx.core.json.JsonObject;
+import org.dromara.hutool.core.lang.Console;
+import org.dromara.hutool.core.util.XmlUtil;
+import org.dromara.hutool.http.HttpUtil;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class XmlTest {
+ @Test
+ void t(){
+ String con = "{\"content\":\"\",\"fromGroup\":\"fmessage\",\"fromUser\":\"fmessage\",\"isSendMsg\":0,\"msgId\":4949224622157906392,\"pid\":1860,\"sign\":\"ab8277a517ed906cf31a64843d4c61d5\",\"signature\":\"\\n\\tv1_ZLVKn3eO\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-25 10:50:39\",\"timestamp\":1684983039,\"type\":37}";
+
+
+ JsonObject entries = new JsonObject(con);
+ String content = entries.getString("content");
+ Document document = XmlUtil.parseXml(content);
+ String encryptusername = document.getDocumentElement().getAttribute("encryptusername");
+
+ String ticket = document.getDocumentElement().getAttribute("ticket");
+ System.out.println(ticket);
+
+ String post = HttpUtil.post("http://127.0.0.1:19088/api/?type=23", new JsonObject().put("v3", encryptusername).put("v4", ticket)
+ .put("type","8")
+ .put("permission","5")
+ .encode());
+ System.out.println(post);
+ }
+
+
+
+ @Test
+ void 接受转账(){
+ String smg = "{\"content\":\"\\n\\n\\n\\n\\n2000\\n\\n\\n\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"fromGroup\":\"wxid_gf1fogt5a0pq22\",\"fromUser\":\"wxid_gf1fogt5a0pq22\",\"isSendMsg\":0,\"msgId\":3157044781598783480,\"path\":\"wxid_4yr8erik0uho22\\\\FileStorage\\\\Cache\\\\2023-05\\\\324af3cccfe197a57506357bff7c85f7\",\"pid\":14268,\"sign\":\"e588b9632126b915e17c5a960e4ef288\",\"signature\":\"\\n\\tv1_LzLnadY1\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:00:15\",\"timestamp\":1685325615,\"type\":49}";
+ JsonObject entries = new JsonObject(smg);
+ String content = entries.getString("content");
+ Document document = XmlUtil.parseXml(content);
+ Element documentElement = document.getDocumentElement();
+ Node transcationid = documentElement.getElementsByTagName("transcationid").item(0);
+ Node transferid = documentElement.getElementsByTagName("transferid").item(0);
+ Console.log(transcationid);
+
+ }
+
+ @Test
+ void 扫码支付(){
+ String smg = "{\"content\":\"\\n \\n 9\\n \\n \\n\\t\\t\\n\\t\\t\\n \\n\\t\\t\\n\\t\\t\\n \\n\\t\\t\\n 1685325848\\n 1\\n 1\\n \\n\\n\\n\\n 0\\n 1\\n10001071012023052901214894726608\\n \\n \\n\",\"fromGroup\":\"wxid_4yr8erik0uho22\",\"fromUser\":\"wxid_4yr8erik0uho22\",\"isSendByPhone\":1,\"isSendMsg\":1,\"msgId\":3869459965780413850,\"pid\":14268,\"sign\":\"2285732a2e5b17729e7eeb8e305add15\",\"signature\":\"\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:04:08\",\"timestamp\":1685325848,\"type\":10002}";
+ JsonObject entries = new JsonObject(smg);
+ String content = entries.getString("content");
+ Document document = XmlUtil.parseXml(content);
+ Element documentElement = document.getDocumentElement();
+ String localName = documentElement.getLocalName();
+ String type = documentElement.getAttribute("type");
+ NodeList outtradeno = documentElement.getElementsByTagName("outtradeno");
+ if (outtradeno.getLength()>0) {
+ Console.log(outtradeno.item(0).getTextContent());
+ }
+
+ }
+ @Test
+ void 扫码支付2duan(){
+ String smg = "{\"content\":\" \\t \\t \\t \\t5 \\t1 0 \\t \\t0 \\t \\t \\t \\t\\t0 \\t\\t \\t\\t \\t\\t \\t\\t \\t\\t \\t \\t \\t \\t \\t \\t\\t \\t\\t\\t \\t\\t\\t \\t\\t\\t\\t \\t\\t\\t\\t0 \\t\\t\\t\\t0 \\t\\t\\t\\t \\t\\t\\t \\t\\t\\t\\t- \\t4 \\t \\t \\t \\t \\t1685325848 \\t \\t \\t \\t0 \\t \\t \\t \\t\\n\\n\\n\\n\\n\\t 0 0 0 \\t \\t \\t1 \\t \\t \\t967 \\t0 0 0 \\t0 \\t \\t \\t\\t0\\t \\t 0 0 0 0 \\t \\t
\\t\\t \\t\\t \\t\\t\\t \\t\\t\\t \\t\\t \\t\\t\\n\\n1685325848\\n\\n\\n1\\n1\\n\\n\\n0\\n\\n\\n1\\n1\\n1\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n\\n\\n \\t\\t1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n967\\n0\\n\\n0\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n967\\n0\\n\\n0\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n1\\n\\n0\\n \\t 0 \\t \\t \\t \\t 01\",\"fromGroup\":\"gh_f0a92aa7146c\",\"fromUser\":\"gh_f0a92aa7146c\",\"isSendMsg\":0,\"msgId\":4286533159027281478,\"pid\":14268,\"sign\":\"65bd099003179e79b95fbbab461d1f6c\",\"signature\":\"\\n\\t3\\n\\t\\n\\t\\t2\\n\\t\\t\\n\\t\\n\\tv1_xf1t7z4t\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:04:09\",\"timestamp\":1685325849,\"type\":49}";
+
+ WxMsgHandle.解析扫码支付第二段(new JsonObject(smg).mapTo(PrivateChatMsg.class));
+ }
+}