16 changed files with 265 additions and 140 deletions
@ -0,0 +1,110 @@ |
|||||
|
package com.gxwebsoft; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import okhttp3.*; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.data.redis.core.StringRedisTemplate; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.time.Instant; |
||||
|
import java.util.concurrent.atomic.AtomicReference; |
||||
|
|
||||
|
@Service |
||||
|
public class WxDev { |
||||
|
|
||||
|
@Value("${wechat.appid}") |
||||
|
private String appId; |
||||
|
@Value("${wechat.secret}") |
||||
|
private String secret; |
||||
|
|
||||
|
private final StringRedisTemplate redisTemplate; |
||||
|
|
||||
|
private final OkHttpClient http = new OkHttpClient(); |
||||
|
private final ObjectMapper om = new ObjectMapper(); |
||||
|
|
||||
|
/** 简单本地缓存 access_token(生产建议放 Redis) */ |
||||
|
private final AtomicReference<String> cachedToken = new AtomicReference<>(); |
||||
|
private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒
|
||||
|
|
||||
|
public WxDev(StringRedisTemplate redisTemplate) { |
||||
|
this.redisTemplate = redisTemplate; |
||||
|
} |
||||
|
|
||||
|
/** 获取/刷新 access_token */ |
||||
|
public String getAccessToken() throws IOException { |
||||
|
long now = Instant.now().getEpochSecond(); |
||||
|
System.out.println("cachedToken.get = " + cachedToken.get()); |
||||
|
if (cachedToken.get() != null && now < tokenExpireEpoch - 60) { |
||||
|
return cachedToken.get(); |
||||
|
} |
||||
|
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token") |
||||
|
.newBuilder() |
||||
|
.addQueryParameter("grant_type", "client_credential") |
||||
|
.addQueryParameter("appid", "wx51962d6ac21f2ed2") |
||||
|
.addQueryParameter("secret", "d821f98de8a6c1ba7bc7e0ee84bcbc8e") |
||||
|
.build(); |
||||
|
Request req = new Request.Builder().url(url).get().build(); |
||||
|
try (Response resp = http.newCall(req).execute()) { |
||||
|
String body = resp.body().string(); |
||||
|
JsonNode json = om.readTree(body); |
||||
|
if (json.has("access_token")) { |
||||
|
String token = json.get("access_token").asText(); |
||||
|
int expiresIn = json.get("expires_in").asInt(7200); |
||||
|
System.out.println("token1 = " + token); |
||||
|
cachedToken.set(token); |
||||
|
tokenExpireEpoch = now + expiresIn; |
||||
|
return token; |
||||
|
} else { |
||||
|
throw new IOException("Get access_token failed: " + body); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** 调用 getwxacodeunlimit,返回图片二进制 */ |
||||
|
public byte[] getUnlimitedCode(String scene, String page, Integer width, |
||||
|
Boolean isHyaline, String envVersion) throws IOException { |
||||
|
String accessToken = getAccessToken(); |
||||
|
System.out.println("accessToken = " + accessToken); |
||||
|
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/wxa/getwxacodeunlimit") |
||||
|
.newBuilder() |
||||
|
.addQueryParameter("access_token", accessToken) |
||||
|
.build(); |
||||
|
|
||||
|
// 构造请求 JSON
|
||||
|
// 注意:scene 仅支持可见字符,长度上限 32,尽量 URL-safe(字母数字下划线等)
|
||||
|
// page 必须是已发布小程序内的路径(不带开头斜杠也可)
|
||||
|
var root = om.createObjectNode(); |
||||
|
root.put("scene", scene); |
||||
|
if (page != null) root.put("page", page); |
||||
|
if (width != null) root.put("width", width); // 默认 430,建议 280~1280
|
||||
|
if (isHyaline != null) root.put("is_hyaline", isHyaline); |
||||
|
if (envVersion != null) root.put("env_version", envVersion); // release/trial/develop
|
||||
|
|
||||
|
RequestBody reqBody = RequestBody.create( |
||||
|
root.toString(), MediaType.parse("application/json; charset=utf-8")); |
||||
|
Request req = new Request.Builder().url(url).post(reqBody).build(); |
||||
|
|
||||
|
try (Response resp = http.newCall(req).execute()) { |
||||
|
if (!resp.isSuccessful()) { |
||||
|
throw new IOException("HTTP " + resp.code() + " calling getwxacodeunlimit"); |
||||
|
} |
||||
|
MediaType ct = resp.body().contentType(); |
||||
|
byte[] bytes = resp.body().bytes(); |
||||
|
// 微信出错时返回 JSON,需要识别一下
|
||||
|
if (ct != null && ct.subtype() != null && ct.subtype().contains("json")) { |
||||
|
String err = new String(bytes); |
||||
|
throw new IOException("WeChat error: " + err); |
||||
|
} |
||||
|
return bytes; // 成功就是图片二进制(PNG)
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void getQrCode() throws IOException { |
||||
|
final byte[] test = getUnlimitedCode("register", "pages/index/index",180,false,"develop"); |
||||
|
System.out.println("test = " + test); |
||||
|
} |
||||
|
} |
@ -1,52 +0,0 @@ |
|||||
package com.gxwebsoft.hjm; |
|
||||
|
|
||||
import com.gxwebsoft.hjm.service.MqttService; |
|
||||
import org.junit.jupiter.api.Test; |
|
||||
import org.springframework.boot.test.context.SpringBootTest; |
|
||||
import org.springframework.test.context.ActiveProfiles; |
|
||||
|
|
||||
import javax.annotation.Resource; |
|
||||
|
|
||||
/** |
|
||||
* MQTT服务测试类 |
|
||||
* |
|
||||
* @author 科技小王子 |
|
||||
* @since 2025-07-02 |
|
||||
*/ |
|
||||
@SpringBootTest |
|
||||
@ActiveProfiles("dev") |
|
||||
public class MqttServiceTest { |
|
||||
|
|
||||
@Resource |
|
||||
private MqttService mqttService; |
|
||||
|
|
||||
@Test |
|
||||
public void testMqttConnection() { |
|
||||
System.out.println("MQTT连接状态: " + mqttService.isConnected()); |
|
||||
System.out.println("MQTT客户端信息: " + mqttService.getClientInfo()); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testMqttReconnect() { |
|
||||
try { |
|
||||
mqttService.reconnect(); |
|
||||
System.out.println("MQTT重连测试完成"); |
|
||||
} catch (Exception e) { |
|
||||
System.err.println("MQTT重连测试失败: " + e.getMessage()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testMqttPublish() { |
|
||||
try { |
|
||||
if (mqttService.isConnected()) { |
|
||||
mqttService.publish("/test/topic", "测试消息"); |
|
||||
System.out.println("MQTT消息发布测试完成"); |
|
||||
} else { |
|
||||
System.out.println("MQTT未连接,跳过发布测试"); |
|
||||
} |
|
||||
} catch (Exception e) { |
|
||||
System.err.println("MQTT消息发布测试失败: " + e.getMessage()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
Loading…
Reference in new issue