From 1038c1b0d4208c7c6499258dfa725fa20a35c067 Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Fri, 22 Aug 2025 18:04:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B9=B3=E6=9D=BF=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E8=8E=B7=E5=8F=96=EF=BC=8C=E4=BC=98=E5=8C=96=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E5=A4=8D=E7=94=A8=E5=8F=8A=E5=A2=9E=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BindSnController.java | 44 +++--- .../controller/DevicesController.java | 7 - .../controller/ManageSnController.java | 3 - .../{PushUtils.java => DevicePushUtils.java} | 37 +++-- .../onekeycall/videotablet/utils/JwtUtil.java | 24 ++- .../videotablet/utils/UserPushUtils.java | 141 ++++++++++++++++++ 6 files changed, 207 insertions(+), 49 deletions(-) rename src/main/java/com/onekeycall/videotablet/utils/{PushUtils.java => DevicePushUtils.java} (85%) create mode 100644 src/main/java/com/onekeycall/videotablet/utils/UserPushUtils.java diff --git a/src/main/java/com/onekeycall/videotablet/controller/BindSnController.java b/src/main/java/com/onekeycall/videotablet/controller/BindSnController.java index f8c540d..3c842f5 100644 --- a/src/main/java/com/onekeycall/videotablet/controller/BindSnController.java +++ b/src/main/java/com/onekeycall/videotablet/controller/BindSnController.java @@ -1,31 +1,21 @@ package com.onekeycall.videotablet.controller; +import com.google.gson.JsonObject; import com.onekeycall.videotablet.entity.DeviceInfo; import com.onekeycall.videotablet.entity.User; import com.onekeycall.videotablet.result.Result; import com.onekeycall.videotablet.service.DeviceSnService; import com.onekeycall.videotablet.service.UserService; import com.onekeycall.videotablet.utils.JwtUtil; -import com.onekeycall.videotablet.utils.PushUtils; +import com.onekeycall.videotablet.utils.DevicePushUtils; import com.onekeycall.videotablet.utils.TextUtils; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.web.bind.annotation.*; -import javax.crypto.SecretKey; import java.util.*; import java.util.concurrent.TimeUnit; @@ -82,11 +72,15 @@ public class BindSnController { return Result.error().message("sn already bind"); } - try { String verifyKey = RandomStringUtils.randomAlphanumeric(32); -// PushUtils.aliyunAsyncPush(verifyKey, userPhone, sn); - PushUtils.tpnsPush(verifyKey, userPhone, sn); + JsonObject params = new JsonObject(); + params.addProperty("verify_key", verifyKey); + params.addProperty("phone", userPhone); + params.addProperty("expire_time", System.currentTimeMillis() + 60 * 1000); + +// PushUtils.aliyunAsyncPush("1", params.toString(), sn); + DevicePushUtils.tpnsPush("1", params.toString(), sn); redisTemplate.opsForValue().set(sn, verifyKey, 1, TimeUnit.MINUTES); return Result.ok().message("send message success"); } catch (Exception e) { @@ -147,7 +141,7 @@ public class BindSnController { oldDeviceInfo.setToken(deviceToken); oldDeviceInfo.setSn(sn); deviceSnService.save(oldDeviceInfo); - Map map = new HashMap<>(); + Map map = new LinkedHashMap<>(); map.put("phone", phone); map.put("device_token", deviceToken); map.put("device_sig", deviceSig); @@ -170,16 +164,32 @@ public class BindSnController { @RequestHeader("Device-Sig") String deviceSig, @RequestParam(value = "sn") String sn) { + if (!jwtUtil.validateDeviceToken(deviceToken, deviceId, sn)) { + return Result.error().message("Invalid token"); + } + DeviceInfo deviceInfo = deviceSnService.findBySn(sn); if (deviceInfo == null) { return Result.notFound().message("sn not found"); } + if (!deviceInfo.getBindSig().equals(deviceSig)) { + return Result.error().message("device sig not match"); + } + if (TextUtils.isEmpty(deviceInfo.getBindPhone())) { return Result.error().message("sn not bind"); } - return Result.ok().message("sn bind"); + Map map = new LinkedHashMap<>(); + map.put("bind_status", 1); + map.put("device_alias", deviceInfo.getDeviceAlias()); + map.put("bind_phone", deviceInfo.getBindPhone()); + map.put("user_id", deviceInfo.getUserId()); + map.put("add_time", deviceInfo.getAddTime()); + map.put("bind_time", deviceInfo.getBindTime()); + + return Result.ok().data(map).message("sn bind"); } } diff --git a/src/main/java/com/onekeycall/videotablet/controller/DevicesController.java b/src/main/java/com/onekeycall/videotablet/controller/DevicesController.java index 0b1f07a..7c7e8d4 100644 --- a/src/main/java/com/onekeycall/videotablet/controller/DevicesController.java +++ b/src/main/java/com/onekeycall/videotablet/controller/DevicesController.java @@ -1,14 +1,7 @@ package com.onekeycall.videotablet.controller; -import com.onekeycall.videotablet.entity.DeviceInfo; -import com.onekeycall.videotablet.entity.User; -import com.onekeycall.videotablet.result.Result; import com.onekeycall.videotablet.service.DeviceSnService; -import com.onekeycall.videotablet.service.UserService; import com.onekeycall.videotablet.utils.JwtUtil; -import com.onekeycall.videotablet.utils.PushUtils; -import com.onekeycall.videotablet.utils.TextUtils; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/onekeycall/videotablet/controller/ManageSnController.java b/src/main/java/com/onekeycall/videotablet/controller/ManageSnController.java index b11ee86..74920bd 100644 --- a/src/main/java/com/onekeycall/videotablet/controller/ManageSnController.java +++ b/src/main/java/com/onekeycall/videotablet/controller/ManageSnController.java @@ -1,15 +1,12 @@ package com.onekeycall.videotablet.controller; import com.onekeycall.videotablet.entity.DeviceInfo; -import com.onekeycall.videotablet.entity.User; import com.onekeycall.videotablet.result.Result; import com.onekeycall.videotablet.service.DeviceSnService; import com.onekeycall.videotablet.service.UserService; import com.onekeycall.videotablet.utils.CXAESUtil; import com.onekeycall.videotablet.utils.JwtUtil; -import com.onekeycall.videotablet.utils.PushUtils; import com.onekeycall.videotablet.utils.TextUtils; -import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/com/onekeycall/videotablet/utils/PushUtils.java b/src/main/java/com/onekeycall/videotablet/utils/DevicePushUtils.java similarity index 85% rename from src/main/java/com/onekeycall/videotablet/utils/PushUtils.java rename to src/main/java/com/onekeycall/videotablet/utils/DevicePushUtils.java index a6790d1..557e640 100644 --- a/src/main/java/com/onekeycall/videotablet/utils/PushUtils.java +++ b/src/main/java/com/onekeycall/videotablet/utils/DevicePushUtils.java @@ -14,7 +14,6 @@ import com.tencent.xinge.bean.MessageAndroid; import com.tencent.xinge.bean.MessageType; import com.tencent.xinge.push.app.PushAppRequest; import darabonba.core.client.ClientOverrideConfiguration; -import org.glassfish.jaxb.core.v2.TODO; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,10 +22,17 @@ import java.util.ArrayList; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class PushUtils { - static Logger logger = LoggerFactory.getLogger(PushUtils.class); +public class DevicePushUtils { + static Logger logger = LoggerFactory.getLogger(DevicePushUtils.class); - public static void aliyunAsyncPush(String verifyKey, String phone, String sn) throws ExecutionException, InterruptedException { + /** + * @param title + * @param jsonString + * @param targetValue + * @throws ExecutionException + * @throws InterruptedException + */ + public static void aliyunAsyncPush(String title, String jsonString, String targetValue) throws ExecutionException, InterruptedException { // HttpClient Configuration /*HttpClient httpClient = new ApacheAsyncHttpClientBuilder() .connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds @@ -64,10 +70,6 @@ public class PushUtils { //.setConnectTimeout(Duration.ofSeconds(30)) ) .build(); - JsonObject params = new JsonObject(); - params.addProperty("verify_key", verifyKey); - params.addProperty("phone", phone); - params.addProperty("time", System.currentTimeMillis()); // Parameter settings for API request PushRequest pushRequest = PushRequest.builder() @@ -75,10 +77,10 @@ public class PushUtils { .pushType("MESSAGE") .deviceType("ANDROID") .target("ALIAS") - .targetValue(sn) - .title("1") + .targetValue(targetValue) + .title(title) .storeOffline(true) - .body(params.toString()) + .body(jsonString) // Request-level configuration rewrite, can set Http request parameters, etc. // .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders())) .build(); @@ -100,7 +102,7 @@ public class PushUtils { client.close(); } - public static void tpnsPush(String verifyKey, String phone, String sn) { + public static void tpnsPush(String title, String jsonString, String targetValue) { XingeApp xingeApp = new XingeApp.Builder() .appId("1500043720") .secretKey("1712af9e3079087447f4c35d2f622b89") @@ -116,22 +118,17 @@ public class PushUtils { //todo 为1时不能接收到不知什么原因 https://console.cloud.tencent.com/tpns/user-tools/1500043720/ap-guangzhou-1 pushAppRequest.setAccount_push_type(0); - JsonObject params = new JsonObject(); - params.addProperty("verify_key", verifyKey); - params.addProperty("phone", phone); - params.addProperty("expire_time", System.currentTimeMillis() + 60 * 1000); - Message message = new Message(); // 推送标题 - message.setTitle("1"); + message.setTitle(title); // 推送内容 - message.setContent(params.toString()); + message.setContent(jsonString); MessageAndroid messageAndroid = new MessageAndroid(); message.setAndroid(messageAndroid); pushAppRequest.setMessage(message); ArrayList accountList = new ArrayList<>(); // 添加绑定账号 - accountList.add(sn); + accountList.add(targetValue); // 设置账号列表 pushAppRequest.setAccount_list(accountList); diff --git a/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java b/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java index a11e3fc..ddbb55b 100644 --- a/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java +++ b/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java @@ -188,6 +188,7 @@ public class JwtUtil { /** * 生成设备签名(首次绑定) + * * @param sn 设备序列号 * @return 设备唯一签名 */ @@ -202,7 +203,8 @@ public class JwtUtil { /** * 生成设备令牌(联合SN+deviceId) - * @param sn 设备序列号 + * + * @param sn 设备序列号 * @param deviceId 设备ID * @return JWT格式令牌 */ @@ -217,11 +219,29 @@ public class JwtUtil { /** * 验证设备令牌 + * * @param deviceToken 设备令牌 * @return 验证结果 */ - public Claims validateDeviceToken(String deviceToken) { + public boolean validateDeviceToken(String deviceToken, String deviceId, String sn) { + Claims claims = parseDeviceToken(deviceToken); + //验证设备ID一致性 + String tokenDeviceId = claims.getSubject(); + if (!deviceId.equals(tokenDeviceId)) { + throw new SecurityException("设备ID不匹配"); + } + + //验证SN一致性 + String tokenSn = claims.get("sn", String.class); + if (!sn.equals(tokenSn)) { + throw new SecurityException("SN不匹配"); + } + + return true; // 通过所有校验 + } + + public Claims parseDeviceToken(String deviceToken) { try { return Jwts.parser() .verifyWith(Keys.hmacShaKeyFor(TABLET_SECRET.getBytes())) diff --git a/src/main/java/com/onekeycall/videotablet/utils/UserPushUtils.java b/src/main/java/com/onekeycall/videotablet/utils/UserPushUtils.java new file mode 100644 index 0000000..2fc2541 --- /dev/null +++ b/src/main/java/com/onekeycall/videotablet/utils/UserPushUtils.java @@ -0,0 +1,141 @@ +package com.onekeycall.videotablet.utils; + +import com.aliyun.auth.credentials.Credential; +import com.aliyun.auth.credentials.provider.StaticCredentialProvider; +import com.aliyun.sdk.service.push20160801.AsyncClient; +import com.aliyun.sdk.service.push20160801.models.PushRequest; +import com.aliyun.sdk.service.push20160801.models.PushResponse; +import com.google.gson.Gson; +import com.tencent.xinge.XingeApp; +import com.tencent.xinge.bean.AudienceType; +import com.tencent.xinge.bean.Message; +import com.tencent.xinge.bean.MessageAndroid; +import com.tencent.xinge.bean.MessageType; +import com.tencent.xinge.push.app.PushAppRequest; +import darabonba.core.client.ClientOverrideConfiguration; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class UserPushUtils { + static Logger logger = LoggerFactory.getLogger(UserPushUtils.class); + + /** + * @param title + * @param jsonString + * @param targetValue + * @throws ExecutionException + * @throws InterruptedException + */ + public static void aliyunAsyncPush(String title, String jsonString, String targetValue) throws ExecutionException, InterruptedException { + // HttpClient Configuration + /*HttpClient httpClient = new ApacheAsyncHttpClientBuilder() + .connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds + .responseTimeout(Duration.ofSeconds(10)) // Set the response timeout time, the default is 20 seconds + .maxConnections(128) // Set the connection pool size + .maxIdleTimeOut(Duration.ofSeconds(50)) // Set the connection pool timeout, the default is 30 seconds + // Configure the proxy + .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("", 9001)) + .setCredentials("", "")) + // If it is an https connection, you need to configure the certificate, or ignore the certificate(.ignoreSSL(true)) + .x509TrustManagers(new X509TrustManager[]{}) + .keyManagers(new KeyManager[]{}) + .ignoreSSL(false) + .build();*/ + + // Configure Credentials authentication information, including ak, secret, token + StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder() + // Please ensure that the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET are set. + .accessKeyId("LTAI5tAffPd7YNsEbNfQSiwU") + .accessKeySecret("6RzsuGceNOUD0WFpRfQRZ8eLXdHtFA") + //.securityToken(System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")) // use STS token + .build()); + + // Configure the Client + AsyncClient client = AsyncClient.builder() + .region("cn-chengdu") // Region ID + //.httpClient(httpClient) // Use the configured HttpClient, otherwise use the default HttpClient (Apache HttpClient) + .credentialsProvider(provider) + //.serviceConfiguration(Configuration.create()) // Service-level configuration + // Client-level configuration rewrite, can set Endpoint, Http request parameters, etc. + .overrideConfiguration( + ClientOverrideConfiguration.create() + // Endpoint 请参考 https://api.aliyun.com/product/Push + .setEndpointOverride("cloudpush.aliyuncs.com") + //.setConnectTimeout(Duration.ofSeconds(30)) + ) + .build(); + + // Parameter settings for API request + PushRequest pushRequest = PushRequest.builder() + .appKey(335580260L) + .pushType("MESSAGE") + .deviceType("ANDROID") + .target("ALIAS") + .targetValue(targetValue) + .title(title) + .storeOffline(true) + .body(jsonString) + // Request-level configuration rewrite, can set Http request parameters, etc. + // .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders())) + .build(); + + // Asynchronously get the return value of the API request + CompletableFuture response = client.push(pushRequest); + // Synchronously get the return value of the API request + PushResponse resp = response.get(); + logger.info(new Gson().toJson(resp)); + // Asynchronous processing of return values + /*response.thenAccept(resp -> { + logger.info(new Gson().toJson(resp)); + }).exceptionally(throwable -> { // Handling exceptions + logger.info(throwable.getMessage()); + return null; + });*/ + + // Finally, close the client + client.close(); + } + + public static void tpnsPush(String title, String jsonString, String targetValue) { + + + + XingeApp xingeApp = new XingeApp.Builder() + .appId("1500043720") + .secretKey("1712af9e3079087447f4c35d2f622b89") + .domainUrl("https://api.tpns.tencent.com/") + .build(); + + PushAppRequest pushAppRequest = new PushAppRequest(); + // 选择推送目标类型 + pushAppRequest.setAudience_type(AudienceType.account); + // 消息类型:通知栏或透传消息 + pushAppRequest.setMessage_type(MessageType.message); + // 选择推送的账号类型 + //todo 为1时不能接收到不知什么原因 https://console.cloud.tencent.com/tpns/user-tools/1500043720/ap-guangzhou-1 + pushAppRequest.setAccount_push_type(0); + + Message message = new Message(); + // 推送标题 + message.setTitle(title); + // 推送内容 + message.setContent(jsonString); + MessageAndroid messageAndroid = new MessageAndroid(); + message.setAndroid(messageAndroid); + pushAppRequest.setMessage(message); + ArrayList accountList = new ArrayList<>(); + // 添加绑定账号 + accountList.add(targetValue); + // 设置账号列表 + pushAppRequest.setAccount_list(accountList); + + JSONObject ret = xingeApp.pushApp(pushAppRequest); + logger.info(ret.toString()); + } + +}