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>
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.apache.http.ssl.SSLContexts;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@@ -432,14 +431,7 @@ public class WxPayConfig {
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(configString)) {
|
||||
// 判断是否为PEM格式的字符串(包含-----BEGIN和-----END标记)
|
||||
if (configString.contains("-----BEGIN") && configString.contains("-----END")) {
|
||||
// PEM格式直接转为字节流,让PemUtils处理
|
||||
configContent = configString.getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
// 纯Base64格式,需要先解码
|
||||
configContent = Base64.getDecoder().decode(configString);
|
||||
}
|
||||
configContent = Base64.getDecoder().decode(configString);
|
||||
return new ByteArrayInputStream(configContent);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
package com.github.binarywang.wxpay.config;
|
||||
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* Test cases for private key format handling in WxPayConfig
|
||||
*/
|
||||
public class WxPayConfigPrivateKeyTest {
|
||||
|
||||
@Test
|
||||
public void testPrivateKeyStringFormat_PemFormat() {
|
||||
WxPayConfig config = new WxPayConfig();
|
||||
|
||||
// Set minimal required configuration
|
||||
config.setMchId("1234567890");
|
||||
config.setApiV3Key("test-api-v3-key-32-characters-long");
|
||||
config.setCertSerialNo("test-serial-number");
|
||||
|
||||
// Test with PEM format private key string that would previously fail
|
||||
String pemKey = "-----BEGIN PRIVATE KEY-----\n" +
|
||||
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2pK3buBufh8Vo\n" +
|
||||
"X4sfYbZ5CcPeGMnVQTGmj0b6\n" +
|
||||
"-----END PRIVATE KEY-----";
|
||||
|
||||
config.setPrivateKeyString(pemKey);
|
||||
|
||||
// This should not throw a "无效的密钥格式" exception immediately
|
||||
// The actual key validation will happen during HTTP client initialization
|
||||
// but at least the format parsing should not fail
|
||||
|
||||
try {
|
||||
// Try to initialize API V3 HTTP client - this might fail for other reasons
|
||||
// (like invalid key content) but should not fail due to format parsing
|
||||
config.initApiV3HttpClient();
|
||||
// If we get here without InvalidKeySpecException, the format detection worked
|
||||
} catch (WxPayException e) {
|
||||
// Check that it's not the specific "无效的密钥格式" error from PemUtils
|
||||
if (e.getCause() != null &&
|
||||
e.getCause().getMessage() != null &&
|
||||
e.getCause().getMessage().contains("无效的密钥格式")) {
|
||||
fail("Private key format detection failed - PEM format was not handled correctly: " + e.getMessage());
|
||||
}
|
||||
// Other exceptions are acceptable for this test since we're using a dummy key
|
||||
} catch (Exception e) {
|
||||
// Check for the specific InvalidKeySpecException that indicates format problems
|
||||
if (e.getCause() != null &&
|
||||
e.getCause().getMessage() != null &&
|
||||
e.getCause().getMessage().contains("无效的密钥格式")) {
|
||||
fail("Private key format detection failed - PEM format was not handled correctly: " + e.getMessage());
|
||||
}
|
||||
// Other exceptions are acceptable for this test since we're using a dummy key
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrivateKeyStringFormat_EmptyString() {
|
||||
WxPayConfig config = new WxPayConfig();
|
||||
|
||||
// Test with empty string - should not cause format errors
|
||||
config.setPrivateKeyString("");
|
||||
|
||||
// This should handle empty strings gracefully
|
||||
// No assertion needed, just ensuring no exceptions during object creation
|
||||
assertNotNull(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrivateKeyStringFormat_NullString() {
|
||||
WxPayConfig config = new WxPayConfig();
|
||||
|
||||
// Test with null string - should not cause format errors
|
||||
config.setPrivateKeyString(null);
|
||||
|
||||
// This should handle null strings gracefully
|
||||
assertNotNull(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrivateCertStringFormat_PemFormat() {
|
||||
WxPayConfig config = new WxPayConfig();
|
||||
|
||||
// Set minimal required configuration
|
||||
config.setMchId("1234567890");
|
||||
config.setApiV3Key("test-api-v3-key-32-characters-long");
|
||||
|
||||
// Test with PEM format certificate string that would previously fail
|
||||
String pemCert = "-----BEGIN CERTIFICATE-----\n" +
|
||||
"MIICdTCCAd4CAQAwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV\n" +
|
||||
"BAsKClRlc3QgQ2VydCBEYXRhMRswGQYDVQQDDBJUZXN0IENlcnRpZmljYXRlQ0Ew\n" +
|
||||
"-----END CERTIFICATE-----";
|
||||
|
||||
config.setPrivateCertString(pemCert);
|
||||
|
||||
// This should not throw a format parsing exception immediately
|
||||
// The actual certificate validation will happen during HTTP client initialization
|
||||
// but at least the format parsing should not fail
|
||||
|
||||
try {
|
||||
// Try to initialize API V3 HTTP client - this might fail for other reasons
|
||||
// (like invalid cert content) but should not fail due to format parsing
|
||||
config.initApiV3HttpClient();
|
||||
// If we get here without Base64 decoding issues, the format detection worked
|
||||
} catch (Exception e) {
|
||||
// Check that it's not the specific Base64 decoding error
|
||||
if (e.getCause() != null &&
|
||||
e.getCause().getMessage() != null &&
|
||||
e.getCause().getMessage().contains("Illegal base64 character")) {
|
||||
fail("Certificate format detection failed - PEM format was not handled correctly: " + e.getMessage());
|
||||
}
|
||||
// Other exceptions are acceptable for this test since we're using a dummy cert
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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