Compare commits
8 Commits
copilot/fi
...
v4.7.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfe1f6dc95 | ||
|
|
c0edd9fd16 | ||
|
|
1788d9071e | ||
|
|
e6a844ab70 | ||
|
|
6fc17201e1 | ||
|
|
ad19f653c7 | ||
|
|
7018dceb1c | ||
|
|
399f3648c0 |
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>WxJava - Weixin/Wechat Java SDK</name>
|
||||
<description>微信开发Java SDK</description>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-graal</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-channel</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-common</artifactId>
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
package me.chanjar.weixin.common.util.locks;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
@@ -72,17 +65,16 @@ public class RedisTemplateSimpleDistributedLock implements Lock {
|
||||
value = UUID.randomUUID().toString();
|
||||
valueThreadLocal.set(value);
|
||||
}
|
||||
RedisSerializer<String> keySerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
|
||||
RedisSerializer<String> valueSerializer = (RedisSerializer<String>) redisTemplate.getValueSerializer();
|
||||
final byte[] keyBytes = Objects.requireNonNull(keySerializer.serialize(key));
|
||||
final byte[] valueBytes = Objects.requireNonNull(valueSerializer.serialize(value));
|
||||
List<Object> redisResults = redisTemplate.executePipelined((RedisCallback<String>) connection -> {
|
||||
connection.set(keyBytes, valueBytes, Expiration.milliseconds(leaseMilliseconds), RedisStringCommands.SetOption.SET_IF_ABSENT);
|
||||
connection.get(keyBytes);
|
||||
return null;
|
||||
});
|
||||
Object currentLockSecret = redisResults.size() > 1 ? redisResults.get(1) : redisResults.get(0);
|
||||
return currentLockSecret != null && currentLockSecret.toString().equals(value);
|
||||
|
||||
// Use high-level StringRedisTemplate API to ensure consistent key serialization
|
||||
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(key, value, leaseMilliseconds, TimeUnit.MILLISECONDS);
|
||||
if (Boolean.TRUE.equals(lockAcquired)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if we already hold the lock (reentrant behavior)
|
||||
String currentValue = redisTemplate.opsForValue().get(key);
|
||||
return value.equals(currentValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package me.chanjar.weixin.common.util.locks;
|
||||
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* 测试 RedisTemplateSimpleDistributedLock 在自定义 Key 序列化时的兼容性
|
||||
*
|
||||
* 这个测试验证修复后的实现确保 tryLock 和 unlock 使用一致的键序列化方式
|
||||
*/
|
||||
@Test(enabled = false) // 默认禁用,需要Redis实例才能运行
|
||||
public class RedisTemplateSimpleDistributedLockSerializationTest {
|
||||
|
||||
private RedisTemplateSimpleDistributedLock redisLock;
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
@BeforeTest
|
||||
public void init() {
|
||||
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
|
||||
connectionFactory.setHostName("127.0.0.1");
|
||||
connectionFactory.setPort(6379);
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
// 创建一个带自定义键序列化的 StringRedisTemplate
|
||||
StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory);
|
||||
|
||||
// 使用自定义键序列化器,模拟在键前面添加前缀的场景
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer() {
|
||||
@Override
|
||||
public byte[] serialize(String string) {
|
||||
if (string == null) return null;
|
||||
// 添加 "System:" 前缀,模拟用户自定义的键序列化
|
||||
return super.serialize("System:" + string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deserialize(byte[] bytes) {
|
||||
if (bytes == null) return null;
|
||||
String result = super.deserialize(bytes);
|
||||
// 移除前缀进行反序列化
|
||||
return result != null && result.startsWith("System:") ? result.substring(7) : result;
|
||||
}
|
||||
});
|
||||
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.redisLock = new RedisTemplateSimpleDistributedLock(redisTemplate, "test_lock_key", 60000);
|
||||
}
|
||||
|
||||
@Test(description = "测试自定义键序列化器下的锁操作一致性")
|
||||
public void testLockConsistencyWithCustomKeySerializer() {
|
||||
// 1. 获取锁应该成功
|
||||
assertTrue(redisLock.tryLock(), "第一次获取锁应该成功");
|
||||
assertNotNull(redisLock.getLockSecretValue(), "锁值应该存在");
|
||||
|
||||
// 2. 验证键已正确存储(通过 redisTemplate 直接查询)
|
||||
String actualValue = redisTemplate.opsForValue().get("test_lock_key");
|
||||
assertEquals(actualValue, redisLock.getLockSecretValue(), "通过 redisTemplate 查询的值应该与锁值相同");
|
||||
|
||||
// 3. 再次尝试获取同一把锁应该成功(可重入)
|
||||
assertTrue(redisLock.tryLock(), "可重入锁应该再次获取成功");
|
||||
|
||||
// 4. 释放锁应该成功
|
||||
redisLock.unlock();
|
||||
assertNull(redisLock.getLockSecretValue(), "释放锁后锁值应该为空");
|
||||
|
||||
// 5. 验证键已被删除
|
||||
actualValue = redisTemplate.opsForValue().get("test_lock_key");
|
||||
assertNull(actualValue, "释放锁后 Redis 中的键应该被删除");
|
||||
|
||||
// 6. 释放已释放的锁应该是安全的
|
||||
redisLock.unlock(); // 不应该抛出异常
|
||||
}
|
||||
|
||||
@Test(description = "测试不同线程使用相同键的锁排他性")
|
||||
public void testLockExclusivityWithCustomKeySerializer() throws InterruptedException {
|
||||
// 第一个锁实例获取锁
|
||||
assertTrue(redisLock.tryLock(), "第一个锁实例应该成功获取锁");
|
||||
|
||||
// 创建第二个锁实例使用相同的键
|
||||
RedisTemplateSimpleDistributedLock anotherLock = new RedisTemplateSimpleDistributedLock(
|
||||
redisTemplate, "test_lock_key", 60000);
|
||||
|
||||
// 第二个锁实例不应该能获取锁
|
||||
assertFalse(anotherLock.tryLock(), "第二个锁实例不应该能获取已被占用的锁");
|
||||
|
||||
// 释放第一个锁
|
||||
redisLock.unlock();
|
||||
|
||||
// 现在第二个锁实例应该能获取锁
|
||||
assertTrue(anotherLock.tryLock(), "第一个锁释放后,第二个锁实例应该能获取锁");
|
||||
|
||||
// 清理
|
||||
anotherLock.unlock();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-cp</artifactId>
|
||||
|
||||
@@ -168,9 +168,13 @@ public class WxCpAgentWorkBench implements Serializable {
|
||||
webview.addProperty("url", this.url);
|
||||
webview.addProperty("jump_url", this.jumpUrl);
|
||||
webview.addProperty("pagepath", this.pagePath);
|
||||
webview.addProperty("enable_webview_click", this.enableWebviewClick);
|
||||
if (this.enableWebviewClick != null) {
|
||||
webview.addProperty("enable_webview_click", this.enableWebviewClick);
|
||||
}
|
||||
webview.addProperty("height", this.height);
|
||||
webview.addProperty("hide_title", this.hideTitle);
|
||||
if (this.hideTitle != null) {
|
||||
webview.addProperty("hide_title", this.hideTitle);
|
||||
}
|
||||
templateObject.add("webview", webview);
|
||||
break;
|
||||
}
|
||||
@@ -236,9 +240,13 @@ public class WxCpAgentWorkBench implements Serializable {
|
||||
webview.addProperty("url", this.url);
|
||||
webview.addProperty("jump_url", this.jumpUrl);
|
||||
webview.addProperty("pagepath", this.pagePath);
|
||||
webview.addProperty("enable_webview_click", this.enableWebviewClick);
|
||||
if (this.enableWebviewClick != null) {
|
||||
webview.addProperty("enable_webview_click", this.enableWebviewClick);
|
||||
}
|
||||
webview.addProperty("height", this.height);
|
||||
webview.addProperty("hide_title", this.hideTitle);
|
||||
if (this.hideTitle != null) {
|
||||
webview.addProperty("hide_title", this.hideTitle);
|
||||
}
|
||||
JsonObject dataObject = new JsonObject();
|
||||
dataObject.addProperty("type", WxCpConsts.WorkBenchType.WEBVIEW);
|
||||
dataObject.add("webview", webview);
|
||||
|
||||
@@ -92,7 +92,7 @@ public class WxCpXmlMessage implements Serializable {
|
||||
private String content;
|
||||
|
||||
@XStreamAlias("MsgId")
|
||||
private Long msgId;
|
||||
private String msgId;
|
||||
|
||||
@XStreamAlias("PicUrl")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
@@ -159,6 +159,14 @@ public class WxCpXmlMessage implements Serializable {
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String MemChangeList;
|
||||
|
||||
@XStreamAlias("LastMemVer")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String lastMemVer;
|
||||
|
||||
@XStreamAlias("CurMemVer")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String curMemVer;
|
||||
|
||||
@XStreamAlias("Source")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String source;
|
||||
|
||||
@@ -72,7 +72,7 @@ public class WxCpXmlMessageTest {
|
||||
assertEquals(wxMessage.getCreateTime(), Long.valueOf(1348831860));
|
||||
assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.TEXT);
|
||||
assertEquals(wxMessage.getContent(), "this is a test");
|
||||
assertEquals(wxMessage.getMsgId(), Long.valueOf(1234567890123456L));
|
||||
assertEquals(wxMessage.getMsgId(), "1234567890123456");
|
||||
assertEquals(wxMessage.getPicUrl(), "this is a url");
|
||||
assertEquals(wxMessage.getMediaId(), "media_id");
|
||||
assertEquals(wxMessage.getFormat(), "Format");
|
||||
@@ -442,4 +442,33 @@ public class WxCpXmlMessageTest {
|
||||
assertThat(wxCpXmlMessage.getJobId()).isEqualTo("jobid_S0MrnndvRG5fadSlLwiBqiDDbM143UqTmKP3152FZk4");
|
||||
assertThat(wxCpXmlMessage.getEvent()).isEqualTo(UPLOAD_MEDIA_JOB_FINISH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test both numeric and string msgId formats to ensure backward compatibility
|
||||
*/
|
||||
public void testMsgIdStringAndNumericFormats() {
|
||||
// Test with numeric msgId (old format)
|
||||
String xmlWithNumeric = "<xml>"
|
||||
+ "<ToUserName><![CDATA[toUser]]></ToUserName>"
|
||||
+ "<FromUserName><![CDATA[fromUser]]></FromUserName>"
|
||||
+ "<CreateTime>1348831860</CreateTime>"
|
||||
+ "<MsgType><![CDATA[text]]></MsgType>"
|
||||
+ "<Content><![CDATA[this is a test]]></Content>"
|
||||
+ "<MsgId>1234567890123456</MsgId>"
|
||||
+ "</xml>";
|
||||
WxCpXmlMessage wxMessageNumeric = WxCpXmlMessage.fromXml(xmlWithNumeric);
|
||||
assertEquals(wxMessageNumeric.getMsgId(), "1234567890123456");
|
||||
|
||||
// Test with string msgId (new format - the actual issue case)
|
||||
String xmlWithString = "<xml>"
|
||||
+ "<ToUserName><![CDATA[toUser]]></ToUserName>"
|
||||
+ "<FromUserName><![CDATA[fromUser]]></FromUserName>"
|
||||
+ "<CreateTime>1348831860</CreateTime>"
|
||||
+ "<MsgType><![CDATA[text]]></MsgType>"
|
||||
+ "<Content><![CDATA[this is a test]]></Content>"
|
||||
+ "<MsgId>CAIQg/PKxgYY2sC9tpuAgAMg9/zKaw==</MsgId>"
|
||||
+ "</xml>";
|
||||
WxCpXmlMessage wxMessageString = WxCpXmlMessage.fromXml(xmlWithString);
|
||||
assertEquals(wxMessageString.getMsgId(), "CAIQg/PKxgYY2sC9tpuAgAMg9/zKaw==");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-miniapp</artifactId>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-mp</artifactId>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-open</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -319,13 +319,6 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
||||
public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String signType) throws WxPayException {
|
||||
try {
|
||||
log.debug("微信支付异步通知请求参数:{}", xmlData);
|
||||
|
||||
// 检测数据格式并给出适当的处理建议
|
||||
if (xmlData != null && xmlData.trim().startsWith("{")) {
|
||||
throw new WxPayException("检测到V3版本的JSON格式通知数据,请使用parseOrderNotifyV3Result方法解析。" +
|
||||
"V3 API需要传入SignatureHeader参数进行签名验证。");
|
||||
}
|
||||
|
||||
WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlData);
|
||||
if (signType == null) {
|
||||
this.switchover(result.getMchId(), result.getAppid());
|
||||
|
||||
@@ -8,11 +8,13 @@ import com.github.binarywang.wxpay.service.WxEntrustPapService;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.github.binarywang.wxpay.util.SignUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author chenliang
|
||||
@@ -26,26 +28,40 @@ public class WxEntrustPapServiceImpl implements WxEntrustPapService {
|
||||
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String mpSign(WxMpEntrustRequest wxMpEntrustRequest) throws WxPayException {
|
||||
wxMpEntrustRequest.checkAndSign(payService.getConfig());
|
||||
StringBuilder signStrTemp = new StringBuilder(payService.getPayBaseUrl() + "/papay/entrustweb");
|
||||
signStrTemp.append("?appid=").append(wxMpEntrustRequest.getAppid());
|
||||
signStrTemp.append("&contract_code=").append(wxMpEntrustRequest.getContractCode());
|
||||
signStrTemp.append("&contract_display_account=").append(URLEncoder.encode(wxMpEntrustRequest.getContractDisplayAccount()));
|
||||
signStrTemp.append("&mch_id=").append(wxMpEntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxMpEntrustRequest.getNotifyUrl()));
|
||||
signStrTemp.append("&plan_id=").append(wxMpEntrustRequest.getPlanId()).append("&outerid=").append(URLEncoder.encode(wxMpEntrustRequest.getOuterId()));
|
||||
signStrTemp.append("&request_serial=").append(wxMpEntrustRequest.getRequestSerial()).append("×tamp=").append(wxMpEntrustRequest.getTimestamp());
|
||||
signStrTemp.append("&version=").append(wxMpEntrustRequest.getVersion()).append("&return_web=").append(wxMpEntrustRequest.getReturnWeb()).append("&sign=").append(wxMpEntrustRequest.getSign());
|
||||
|
||||
signStrTemp.append("&contract_display_account=")
|
||||
.append(URLEncoder.encode(wxMpEntrustRequest.getContractDisplayAccount(), StandardCharsets.UTF_8.name()));
|
||||
signStrTemp.append("&mch_id=").append(wxMpEntrustRequest.getMchId()).append("¬ify_url=")
|
||||
.append(URLEncoder.encode(wxMpEntrustRequest.getNotifyUrl(), StandardCharsets.UTF_8.name()));
|
||||
signStrTemp.append("&plan_id=").append(wxMpEntrustRequest.getPlanId());
|
||||
signStrTemp.append("&request_serial=").append(wxMpEntrustRequest.getRequestSerial()).append("×tamp=")
|
||||
.append(wxMpEntrustRequest.getTimestamp());
|
||||
// 根据微信支付文档,returnWeb字段只在值为1时需要添加到URL参数中,表示返回签约页面的referrer url
|
||||
if (wxMpEntrustRequest.getReturnWeb() != null && wxMpEntrustRequest.getReturnWeb() == 1) {
|
||||
signStrTemp.append("&return_web=").append(wxMpEntrustRequest.getReturnWeb());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(wxMpEntrustRequest.getOuterId())) {
|
||||
signStrTemp.append("&outerid=").append(URLEncoder.encode(wxMpEntrustRequest.getOuterId(), StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
signStrTemp.append("&version=").append(wxMpEntrustRequest.getVersion()).append("&sign=")
|
||||
.append(wxMpEntrustRequest.getSign());
|
||||
return signStrTemp.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String maSign(WxMaEntrustRequest wxMaEntrustRequest) throws WxPayException {
|
||||
wxMaEntrustRequest.checkAndSign(payService.getConfig());
|
||||
wxMaEntrustRequest.setNotifyUrl(URLEncoder.encode(wxMaEntrustRequest.getNotifyUrl()));
|
||||
wxMaEntrustRequest.setNotifyUrl(URLEncoder.encode(wxMaEntrustRequest.getNotifyUrl(), StandardCharsets.UTF_8.name()));
|
||||
return wxMaEntrustRequest.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public WxH5EntrustResult h5Sign(WxH5EntrustRequest wxH5EntrustRequest) throws WxPayException {
|
||||
wxH5EntrustRequest.checkAndSign(payService.getConfig());
|
||||
@@ -64,11 +80,11 @@ public class WxEntrustPapServiceImpl implements WxEntrustPapService {
|
||||
StringBuilder strBuilder = new StringBuilder(url);
|
||||
strBuilder.append("?appid=").append(wxH5EntrustRequest.getAppid());
|
||||
strBuilder.append("&contract_code=").append(wxH5EntrustRequest.getContractCode());
|
||||
strBuilder.append("&contract_display_account=").append(URLEncoder.encode(wxH5EntrustRequest.getContractDisplayAccount()));
|
||||
strBuilder.append("&mch_id=").append(wxH5EntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxH5EntrustRequest.getNotifyUrl()));
|
||||
strBuilder.append("&contract_display_account=").append(URLEncoder.encode(wxH5EntrustRequest.getContractDisplayAccount(), StandardCharsets.UTF_8.name()));
|
||||
strBuilder.append("&mch_id=").append(wxH5EntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxH5EntrustRequest.getNotifyUrl(), StandardCharsets.UTF_8.name()));
|
||||
strBuilder.append("&plan_id=").append(wxH5EntrustRequest.getPlanId());
|
||||
if (StringUtils.isNotEmpty(wxH5EntrustRequest.getOuterId())) {
|
||||
strBuilder.append("&outerid=").append(URLEncoder.encode(wxH5EntrustRequest.getOuterId()));
|
||||
strBuilder.append("&outerid=").append(URLEncoder.encode(wxH5EntrustRequest.getOuterId(), StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(wxH5EntrustRequest.getReturnAppid())) {
|
||||
strBuilder.append("&return_appid=").append(wxH5EntrustRequest.getReturnAppid());
|
||||
|
||||
@@ -80,11 +80,11 @@ public class AesUtils {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
|
||||
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(), "AES");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce.getBytes());
|
||||
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
cipher.updateAAD(associatedData.getBytes());
|
||||
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
|
||||
@@ -82,35 +82,4 @@ public class WxPayOrderNotifyResultTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that JSON format input throws a helpful error message.
|
||||
*/
|
||||
@Test
|
||||
public void testFromXMLWithJsonShouldGiveHelpfulError() {
|
||||
String jsonString = "{\n" +
|
||||
" \"id\": \"EV-2018022511223320873\",\n" +
|
||||
" \"create_time\": \"2015-05-20T13:29:35+08:00\",\n" +
|
||||
" \"resource_type\": \"encrypt-resource\",\n" +
|
||||
" \"event_type\": \"TRANSACTION.SUCCESS\",\n" +
|
||||
" \"summary\": \"支付成功\",\n" +
|
||||
" \"resource\": {\n" +
|
||||
" \"algorithm\": \"AEAD_AES_256_GCM\",\n" +
|
||||
" \"ciphertext\": \"test\",\n" +
|
||||
" \"associated_data\": \"transaction\",\n" +
|
||||
" \"nonce\": \"test\"\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
try {
|
||||
WxPayOrderNotifyResult.fromXML(jsonString);
|
||||
Assert.fail("Expected exception for JSON input");
|
||||
} catch (Exception e) {
|
||||
// Verify that the error message mentions whitespace/XML parsing issues
|
||||
// This is the original XStream error that would occur
|
||||
Assert.assertTrue(e.getMessage().contains("whitespace") ||
|
||||
e.getMessage().contains("XmlPull") ||
|
||||
e.getMessage().contains("START_DOCUMENT"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.7.B</version>
|
||||
<version>4.7.8.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-qidian</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user