Browse Source
- 新增 getQRCodeUnlimited 方法生成小程序码 - 添加 getLocalAccessToken 方法获取微信 access_token - 更新 WxLoginController 以使用新的二维码生成逻辑- 移除 MqttServiceTest 类,增加 WxDev 类用于微信相关测试 - 更新 Dockerfile 和 docker-compose.yml 以适应新的功能需求main
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