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<![CDATA[微信转账]]>\\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<![CDATA[微信支付收款0.01元(朋友到店)]]> \\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<![CDATA[微信支付收款0.01元(朋友到店)]]> \\t \\t \\t \\t1685325848 \\t \\t \\t \\t0 \\t \\t \\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<![CDATA[收款到账通知]]>\\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)); + } +}