Merge pull request #227 from PathfinderAx/master

Java版增加Maven版
This commit is contained in:
Changhua 2024-09-22 16:27:51 +08:00 committed by GitHub
commit cfa11de6c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 34478 additions and 1 deletions

View File

@ -77,7 +77,7 @@ pip install --upgrade wcferry
* [go_wcf_http](clients/go_wcf_http/README.MD)(基于 Go
### Java
* [java](clients/java/README.MD)
* [java](clients/java/wcferry/README.MD)
### NodeJS
* [wcferry-node](https://github.com/dr-forget/wcferry-node)

33
clients/java/wcferry-mvn/.gitignore vendored Normal file
View File

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

72
clients/java/wcferry-mvn/README.MD vendored Normal file
View File

@ -0,0 +1,72 @@
# WeChatFerry Java 客户端 maven版
⚠️ **只支持 Windows** ⚠️
## 快速使用
### 环境准备
| 名称 | 版本 |
|-------|-----------|
| JDK | 1.8+ |
| Maven | 3.8+ |
| 微信 | 3.9.10.27 |
### 下载文件
* 下载 [最新发布的文件](https://github.com/lich0821/WeChatFerry/releases/latest)
### 使用惯用 IDE打开工程
可以直接以WeChatFerry为根目录打开
或者以WeChatFerry/clients/java/wcferry-mvn为根目录打开
### 添加Maven
找到 WeChatFerry/clients/java/wcferry-mvn/pom.xml 文件右键添加到Maven中会自动下载依赖
### 替换对应版本的dll
把刚下载的最新发布文件解压到本项目中的 dll 文件目录下,直接替换原因文件即可
### 修改配置文件
配置文件src/main/resources/application.yml
根据自己的dll目录位置修改配置文件
```yaml
# 本服务参数
wcferry:
# DLL文件位置
dll-path: E:\WeChatFerry\clients\java\wcferry-mvn\dll\sdk.dll
# socket端口
socket-port: 10086
```
### 编译运行
找到 src/main/java/com/iamteer/Main.java 类
直接启动即可
### 访问
启动后springboot自身的端口为 9201 socket的端口为 10086
## 参与开发
### 核心依赖
| 依赖 | 版本 | 说明 |
|---------------|--------|----------|
| Spring Boot | 2.7.18 | 基础框架 |
| protobuf-java | 3.22.2 | rpc |
| jna | 5.6.0 | 态访问系统本地库 |
| nng-java | 1.4.0 | 本地包 |

64
clients/java/wcferry-mvn/pom.xml vendored Normal file
View File

@ -0,0 +1,64 @@
<?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>2.7.18</version>
</parent>
<artifactId>wcferry-mvn</artifactId>
<packaging>jar</packaging>
<name>wcferry-mvn</name>
<description>wcferry客户端Java-Maven版</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.22.2</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.6.0</version>
</dependency>
<!-- 引入本地lib -->
<dependency>
<groupId>io.sisu.nng</groupId>
<artifactId>nng-java</artifactId>
<version>1.4.0-SNAPSHOT</version>
<!-- system表示依赖不是由maven仓库而是本地的jar包 -->
<scope>system</scope>
<!-- jar包位置 -->
<systemPath>${project.basedir}/src/main/resources/libs/nng-java-1.4.0-SNAPSHOT.jar</systemPath>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,536 @@
package com.iamteer;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import com.sun.jna.Native;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.iamteer.Wcf.*;
import io.sisu.nng.Socket;
import io.sisu.nng.pair.Pair1Socket;
public class Client {
private static final Logger logger = LoggerFactory.getLogger(Client.class);
private static final int BUFFER_SIZE = 16 * 1024 * 1024; // 16M
private Socket cmdSocket = null;
private Socket msgSocket = null;
private static String DEFAULT_HOST = "127.0.0.1";
private static int PORT = 10086;
private static String CMDURL = "tcp://%s:%s";
private static String DEFAULT_DLL_PATH = System.getProperty("user.dir") + "\\dll\\sdk.dll";
private boolean isReceivingMsg = false;
private boolean isLocalHostPort = false;
private BlockingQueue<WxMsg> msgQ;
private String host;
private int port;
private String dllPath;
public Client() {
this(DEFAULT_HOST, PORT, false, DEFAULT_DLL_PATH);
}
public Client(int port, String dllPath) {
this(DEFAULT_HOST, port, false, dllPath);
}
public Client(String host, int port, boolean debug, String dllPath) {
this.host = host;
this.port = port;
this.dllPath = dllPath;
SDK INSTANCE = Native.load(dllPath, SDK.class);
int status = INSTANCE.WxInitSDK(debug, port);
if (status != 0) {
logger.error("启动 RPC 失败: {}", status);
System.exit(-1);
}
connectRPC(String.format(CMDURL, host, port), INSTANCE);
if (DEFAULT_HOST.equals(host) || "localhost".equalsIgnoreCase(host)) {
isLocalHostPort = true;
}
}
public void connectRPC(String url, SDK INSTANCE) {
try {
cmdSocket = new Pair1Socket();
cmdSocket.dial(url);
//logger.info("请点击登录微信");
while (!isLogin()) { // 直到登录成功
waitMs(1000);
}
} catch (Exception e) {
logger.error("连接 RPC 失败: ", e);
System.exit(-1);
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.info("关闭...");
diableRecvMsg();
if (isLocalHostPort) {
INSTANCE.WxDestroySDK();
}
}));
}
private Response sendCmd(Request req) {
try {
ByteBuffer bb = ByteBuffer.wrap(req.toByteArray());
cmdSocket.send(bb);
ByteBuffer ret = ByteBuffer.allocate(BUFFER_SIZE);
long size = cmdSocket.receive(ret, true);
return Response.parseFrom(Arrays.copyOfRange(ret.array(), 0, (int) size));
} catch (Exception e) {
logger.error("命令调用失败: ", e);
return null;
}
}
/**
* 当前微信客户端是否登录微信号
*
* @return
*/
public boolean isLogin() {
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_IS_LOGIN_VALUE).build();
Response rsp = sendCmd(req);
if (rsp != null) {
return rsp.getStatus() == 1;
}
return false;
}
/**
* 获得微信客户端登录的微信ID
*
* @return
*/
public String getSelfWxid() {
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_GET_SELF_WXID_VALUE).build();
Response rsp = sendCmd(req);
if (rsp != null) {
return rsp.getStr();
}
return "";
}
/**
* 获取所有消息类型
*
* @return
*/
public Map<Integer, String> getMsgTypes() {
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_GET_MSG_TYPES_VALUE).build();
Response rsp = sendCmd(req);
if (rsp != null) {
return rsp.getTypes().getTypesMap();
}
return Wcf.MsgTypes.newBuilder().build().getTypesMap();
}
/**
* 获取所有联系人
* "fmessage": "朋友推荐消息",
* "medianote": "语音记事本",
* "floatbottle": "漂流瓶",
* "filehelper": "文件传输助手",
* "newsapp": "新闻",
*
* @return
*/
public List<RpcContact> getContacts() {
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_GET_CONTACTS_VALUE).build();
Response rsp = sendCmd(req);
if (rsp != null) {
return rsp.getContacts().getContactsList();
}
return Wcf.RpcContacts.newBuilder().build().getContactsList();
}
/**
* 获取sql执行结果
*
* @param db 数据库名
* @param sql 执行的sql语句
* @return
*/
public List<DbRow> querySql(String db, String sql) {
DbQuery dbQuery = DbQuery.newBuilder().setSql(sql).setDb(db).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_EXEC_DB_QUERY_VALUE)
.setQuery(dbQuery).build();
Response rsp = sendCmd(req);
if (rsp != null) {
return rsp.getRows().getRowsList();
}
return null;
}
/**
* 获取所有数据库名
*
* @return
*/
public List<String> getDbNames() {
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_GET_DB_NAMES_VALUE).build();
Response rsp = sendCmd(req);
if (rsp != null) {
return rsp.getDbs().getNamesList();
}
return Wcf.DbNames.newBuilder().build().getNamesList();
}
/**
* 获取指定数据库中的所有表
*
* @param db
* @return
*/
public Map<String, String> getDbTables(String db) {
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_GET_DB_TABLES_VALUE).setStr(db)
.build();
Response rsp = sendCmd(req);
Map<String, String> tables = new HashMap<>();
if (rsp != null) {
for (DbTable tbl : rsp.getTables().getTablesList()) {
tables.put(tbl.getName(), tbl.getSql());
}
}
return tables;
}
/**
* @param msg: 消息内容如果是 @ 消息则需要有跟 @ 的人数量相同的 @
* @param receiver: 消息接收人私聊为 wxidwxid_xxxxxxxxxxxxxx群聊为
* roomidxxxxxxxxxx@chatroom
* @param aters: 群聊时要 @ 的人私聊时为空字符串多个用逗号分隔@所有人
* notify@all必须是群主或者管理员才有权限
* @return int
* @Description 发送文本消息
* @author Changhua
* @example sendText(" Hello @ 某人1 @ 某人2 ", " xxxxxxxx @ chatroom ",
* "wxid_xxxxxxxxxxxxx1,wxid_xxxxxxxxxxxxx2");
**/
public int sendText(String msg, String receiver, String aters) {
Wcf.TextMsg textMsg = Wcf.TextMsg.newBuilder().setMsg(msg).setReceiver(receiver).setAters(aters)
.build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_SEND_TXT_VALUE).setTxt(textMsg)
.build();
logger.debug("sendText: {}", bytesToHex(req.toByteArray()));
Response rsp = sendCmd(req);
int ret = -1;
if (rsp != null) {
ret = rsp.getStatus();
}
return ret;
}
/**
* 发送图片消息
*
* @param path 图片地址
* @param receiver 接收者微信id
* @return 发送结果状态码
*/
public int sendImage(String path, String receiver) {
Wcf.PathMsg pathMsg = Wcf.PathMsg.newBuilder().setPath(path).setReceiver(receiver).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_SEND_IMG_VALUE).setFile(pathMsg)
.build();
logger.debug("sendImage: {}", bytesToHex(req.toByteArray()));
Response rsp = sendCmd(req);
int ret = -1;
if (rsp != null) {
ret = rsp.getStatus();
}
return ret;
}
/**
* 发送文件消息
*
* @param path 文件地址
* @param receiver 接收者微信id
* @return 发送结果状态码
*/
public int sendFile(String path, String receiver) {
Wcf.PathMsg pathMsg = Wcf.PathMsg.newBuilder().setPath(path).setReceiver(receiver).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_SEND_FILE_VALUE).setFile(pathMsg)
.build();
logger.debug("sendFile: {}", bytesToHex(req.toByteArray()));
Response rsp = sendCmd(req);
int ret = -1;
if (rsp != null) {
ret = rsp.getStatus();
}
return ret;
}
/**
* 发送Xml消息
*
* @param receiver 接收者微信id
* @param xml xml内容
* @param path
* @param type
* @return 发送结果状态码
*/
public int sendXml(String receiver, String xml, String path, int type) {
Wcf.XmlMsg xmlMsg = Wcf.XmlMsg.newBuilder().setContent(xml).setReceiver(receiver).setPath(path)
.setType(type).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_SEND_XML_VALUE).setXml(xmlMsg)
.build();
logger.debug("sendXml: {}", bytesToHex(req.toByteArray()));
Response rsp = sendCmd(req);
int ret = -1;
if (rsp != null) {
ret = rsp.getStatus();
}
return ret;
}
/**
* 发送表情消息
*
* @param path 表情路径
* @param receiver 消息接收者
* @return 发送结果状态码
*/
public int sendEmotion(String path, String receiver) {
Wcf.PathMsg pathMsg = Wcf.PathMsg.newBuilder().setPath(path).setReceiver(receiver).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_SEND_EMOTION_VALUE)
.setFile(pathMsg).build();
logger.debug("sendEmotion: {}", bytesToHex(req.toByteArray()));
Response rsp = sendCmd(req);
int ret = -1;
if (rsp != null) {
ret = rsp.getStatus();
}
return ret;
}
/**
* 接收好友请求
*
* @param v3 xml.attrib["encryptusername"]
* @param v4 xml.attrib["ticket"]
* @return 结果状态码
*/
public int acceptNewFriend(String v3, String v4) {
int ret = -1;
Verification verification = Verification.newBuilder().setV3(v3).setV4(v4).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_ACCEPT_FRIEND_VALUE)
.setV(verification).build();
Response rsp = sendCmd(req);
if (rsp != null) {
ret = rsp.getStatus();
}
return ret;
}
/**
* 添加群成员为微信好友
*
* @param roomID 群ID
* @param wxIds 要加群的人列表逗号分隔
* @return 1 为成功其他失败
*/
public int addChatroomMembers(String roomID, String wxIds) {
int ret = -1;
MemberMgmt memberMgmt = MemberMgmt.newBuilder().setRoomid(roomID).setWxids(wxIds).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_ADD_ROOM_MEMBERS_VALUE)
.setM(memberMgmt).build();
Response rsp = sendCmd(req);
if (rsp != null) {
ret = rsp.getStatus();
}
return ret;
}
/**
* 解密图片
*
* @param srcPath 加密的图片路径
* @param dstPath 解密的图片路径
* @return 是否成功
*/
public boolean decryptImage(String srcPath, String dstPath) {
int ret = -1;
DecPath build = DecPath.newBuilder().setSrc(srcPath).setDst(dstPath).build();
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_DECRYPT_IMAGE_VALUE)
.setDec(build).build();
Response rsp = sendCmd(req);
if (rsp != null) {
ret = rsp.getStatus();
}
return ret == 1;
}
/**
* 获取个人信息
*
* @return 个人信息
*/
public UserInfo getUserInfo() {
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_GET_USER_INFO_VALUE).build();
Response rsp = sendCmd(req);
if (rsp != null) {
return rsp.getUi();
}
return null;
}
public boolean getIsReceivingMsg() {
return isReceivingMsg;
}
public WxMsg getMsg() {
try {
return msgQ.take();
} catch (Exception e) {
// TODO: handle exception
return null;
}
}
/**
* 判断是否是艾特自己的消息
*
* @param wxMsgXml
* @param wxMsgContent
* @return
*/
public boolean isAtMeMsg(String wxMsgXml, String wxMsgContent) {
String format = String.format("<atuserlist><![CDATA[%s]]></atuserlist>", getSelfWxid());
boolean isAtAll = wxMsgContent.startsWith("@所有人") || wxMsgContent.startsWith("@all");
if (wxMsgXml.contains(format) && !isAtAll) {
return true;
}
return false;
}
private void listenMsg(String url) {
try {
msgSocket = new Pair1Socket();
msgSocket.dial(url);
msgSocket.setReceiveTimeout(2000); // 2 秒超时
} catch (Exception e) {
logger.error("创建消息 RPC 失败", e);
return;
}
ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE);
while (isReceivingMsg) {
try {
long size = msgSocket.receive(bb, true);
WxMsg wxMsg = Response.parseFrom(Arrays.copyOfRange(bb.array(), 0, (int) size)).getWxmsg();
msgQ.put(wxMsg);
} catch (Exception e) {
// 多半是超时忽略吧
}
}
try {
msgSocket.close();
} catch (Exception e) {
logger.error("关闭连接失败", e);
}
}
public void enableRecvMsg(int qSize) {
if (isReceivingMsg) {
return;
}
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_ENABLE_RECV_TXT_VALUE).build();
Response rsp = sendCmd(req);
if (rsp == null) {
logger.error("启动消息接收失败");
isReceivingMsg = false;
return;
}
isReceivingMsg = true;
msgQ = new ArrayBlockingQueue<>(qSize);
String msgUrl = "tcp://" + this.host + ":" + (this.port + 1);
Thread thread = new Thread(() -> listenMsg(msgUrl));
thread.start();
}
public int diableRecvMsg() {
if (!isReceivingMsg) {
return 1;
}
int ret = -1;
Request req = Request.newBuilder().setFuncValue(Functions.FUNC_DISABLE_RECV_TXT_VALUE).build();
Response rsp = sendCmd(req);
if (rsp != null) {
ret = rsp.getStatus();
if (ret == 0) {
isReceivingMsg = false;
}
}
return ret;
}
public void waitMs(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
public void printContacts(List<RpcContact> contacts) {
for (RpcContact c : contacts) {
int value = c.getGender();
String gender;
if (value == 1) {
gender = "";
} else if (value == 2) {
gender = "";
} else {
gender = "未知";
}
logger.info("{}, {}, {}, {}, {}, {}, {}", c.getWxid(), c.getName(), c.getCode(),
c.getCountry(), c.getProvince(), c.getCity(), gender);
}
}
public void printWxMsg(WxMsg msg) {
logger.info("{}[{}]:{}:{}:{}\n{}", msg.getSender(), msg.getRoomid(), msg.getId(), msg.getType(),
msg.getXml().replace("\n", "").replace("\t", ""), msg.getContent());
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public void keepRunning() {
while (true) {
waitMs(1000);
}
}
}

View File

@ -0,0 +1,30 @@
package com.iamteer;
import com.sun.jna.Library;
/**
* SDK.dll的接口类
*
* @Description
* @Author xinggq
* @Date 2024/7/10
*/
public interface SDK extends Library {
/**
* 初始化SDK
*
* @param debug
* @param port
* @return
*/
int WxInitSDK(boolean debug, int port);
/**
* 退出SDK
*
* @return
*/
int WxDestroySDK();
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
package com.iamteer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
*
* @author chandler
* @date 2024-09-21 12:19
*/
@SpringBootApplication
public class WcferryApplication {
public static void main(String[] args) {
SpringApplication.run(WcferryApplication.class, args);
}
}

View File

@ -0,0 +1,29 @@
package com.iamteer.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
/**
* 配置文件-UAM模块的外部接口
*
* @author chandler
* @date 2024-04-26 21:35
*/
@Data
@Component
@ConfigurationProperties(prefix = "wcferry")
public class WcferryProperties {
/**
* dll文件位置
*/
private String dllPath;
/**
* 端口
*/
private Integer socketPort;
}

View File

@ -0,0 +1,96 @@
package com.iamteer.runner;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import com.iamteer.Client;
import com.iamteer.config.WcferryProperties;
import lombok.extern.slf4j.Slf4j;
/**
* 启动回调-调用微信
*
* @author chandler
* @date 2024-09-21 12:21
*/
@Slf4j
@Component
public class WechatRunner implements ApplicationRunner {
@Resource
private WcferryProperties properties;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(">>>服务启动第一个开始执行的任务<<<<");
runWechat();
}
private void runWechat() {
log.debug("测试:端口:{},地址:{}", properties.getSocketPort(), properties.getDllPath());
// 连接远程 RPC
// Client client = new Client("127.0.0.1", 10086);
// 本地启动 RPC
// Client client = new Client(); // 默认 10086 端口
// Client client = new Client(10088,true); // 也可以指定端口
Client client = new Client(properties.getSocketPort(), properties.getDllPath()); // 默认 10086 端口
// 是否已登录
log.info("isLogin: {}", client.isLogin());
// 登录账号 wxid
log.info("wxid: {}", client.getSelfWxid());
// 消息类型
log.info("message types: {}", client.getMsgTypes());
// 所有联系人包括群聊公众号好友
client.printContacts(client.getContacts());
// 获取数据库
log.info("dbs: {}", client.getDbNames());
// 获取数据库下的表
String db = "MicroMsg.db";
log.info("tables in {}: {}", db, client.getDbTables(db));
// 发送文本消息aters 是要 @ wxid多个用逗号分隔消息里@的数量要与aters里的数量对应
client.sendText("Hello", "filehelper", "");
// client.sendText("Hello @某人1 @某人2", "xxxxxxxx@chatroom", "wxid_xxxxxxxxxxxxx1,wxid_xxxxxxxxxxxxx2");
// 发送图片消息图片必须要存在
client.sendImage("C:\\Projs\\WeChatFerry\\TEQuant.jpeg", "filehelper");
// 发送文件消息文件必须要存在
client.sendFile("C:\\Projs\\WeChatFerry\\README.MD", "filehelper");
String xml =
"<?xml version=\"1.0\"?><msg><appmsg appid=\"\" sdkver=\"0\"><title>叮当药房24小时服务28分钟送药到家</title><des>叮当快药首家承诺范围内28分钟送药到家叮当快药核心区域内7*24小时全天候服务送药上门叮当快药官网为您提供快捷便利正品低价安全放心的购药、送药服务体验。</des><action>view</action><type>33</type><showtype>0</showtype><content /><url>https://mp.weixin.qq.com/mp/waerrpage?appid=wxc2edadc87077fa2a&amp;type=upgrade&amp;upgradetype=3#wechat_redirect</url><dataurl /><lowurl /><lowdataurl /><recorditem /><thumburl /><messageaction /><md5>7f6f49d301ebf47100199b8a4fcf4de4</md5><extinfo /><sourceusername>gh_c2b88a38c424@app</sourceusername><sourcedisplayname>叮当快药 药店送药到家夜间买药</sourcedisplayname><commenturl /><appattach><totallen>0</totallen><attachid /><emoticonmd5></emoticonmd5><fileext>jpg</fileext><filekey>da0e08f5c7259d03da150d5e7ca6d950</filekey><cdnthumburl>3057020100044b30490201000204e4c0232702032f4ef20204a6bace6f02046401f62d042430326337303430352d333734332d343362652d623335322d6233333566623266376334620204012400030201000405004c537600</cdnthumburl><aeskey>0db26456caf243fbd4efb99058a01d66</aeskey><cdnthumbaeskey>0db26456caf243fbd4efb99058a01d66</cdnthumbaeskey><encryver>1</encryver><cdnthumblength>61558</cdnthumblength><cdnthumbheight>100</cdnthumbheight><cdnthumbwidth>100</cdnthumbwidth></appattach><weappinfo><pagepath>pages/index/index.html</pagepath><username>gh_c2b88a38c424@app</username><appid>wxc2edadc87077fa2a</appid><version>197</version><type>2</type><weappiconurl>http://wx.qlogo.cn/mmhead/Q3auHgzwzM4727n0NQ0ZIPQPlfp15m1WLsnrXbo1kLhFGcolgLyc0A/96</weappiconurl><appservicetype>0</appservicetype><shareId>1_wxc2edadc87077fa2a_29177e9a9b918cb9e75964f80bb8f32e_1677849476_0</shareId></weappinfo><websearch /></appmsg><fromusername>wxid_eob5qfcrv4zd22</fromusername><scene>0</scene><appinfo><version>1</version><appname /></appinfo><commenturl /></msg>";
client.sendXml("filehelper", xml, "", 0x21);
// 发送表情消息gif 必须要存在
client.sendEmotion("C:\\Projs\\WeChatFerry\\emo.gif", "filehelper");
// 接收消息并调用 printWxMsg 处理
client.enableRecvMsg(100);
Thread thread = new Thread(new Runnable() {
public void run() {
while (client.getIsReceivingMsg()) {
client.printWxMsg(client.getMsg());
}
}
});
thread.start();
// client.diableRecvMsg(); // 需要停止时调用
client.keepRunning();
}
}

View File

@ -0,0 +1,19 @@
# 配置文件
# 服务端配置
server:
# 端口设置
port: 9201
spring:
# 配置应用信息
application:
# 应用名
name: wcferry-mvn
# 本服务参数
wcferry:
# DLL文件位置
dll-path: E:\WeChatFerry\clients\java\wcferry-mvn\dll\sdk.dll
# socket端口
socket-port: 10086

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false">
<springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>
<property name="log.path" value="logs/${spring.application.name}"/>
<property name="log.name" value="${spring.application.name}"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!-- 此日志文件只记录error级别的 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<!--
DEBUG输出调试信息指出细粒度信息事件对调试应用程序是非常有帮助的。
INFO 输出提示信息;消息在粗粒度级别上突出强调应用程序的运行过程。
WARN 输出警告信息;表明会出现潜在错误的情形。
ERROR输出错误信息指出虽然发生错误事件但仍然不影响系统的继续运行。
FATAL 输出致命错误;指出每个严重的错误事件将会导致应用程序的退出。
ALL level打开所有日志记录开关是最低等级的用于打开所有日志记录。
OFF level关闭所有日志记录开关是最高等级的用于关闭所有日志记录。
日志级别(按照范围从小到大排序)OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL
范围大的会包含范围小的例如日志设置为INFO级别的话则FATAL、ERROR、WARN、INFO的日志开关都是打开的而DEBUG的日志开关将是关闭的。
-->
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>
<logger>仅有一个name属性
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别大小写无关TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
如果未设置此属性那么当前logger将会继承上级的级别。
-->
<!-- 日志监听器 屏蔽 -->
<logger name="org.springframework.boot.autoconfigure.logging" level="INFO">
<appender-ref ref="console"/>
</logger>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="DEBUG">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
</root>
</configuration>