From b65dffd3140ef924579ccd1ef0505d6696303109 Mon Sep 17 00:00:00 2001 From: liyongjie <1318551549@qq.com> Date: Thu, 18 Apr 2024 22:46:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 38 ++ .../java/com/muyu/LoadCenterApplication.java | 18 + .../muyu/common/aliyun/AliYunEcsService.java | 161 +++++++ .../muyu/common/aliyun/config/AliConfig.java | 42 ++ .../common/aliyun/config/InstanceConfig.java | 59 +++ .../common/aliyun/config/MqttInfoClient.java | 38 ++ .../common/aliyun/module/EcsSelectModule.java | 27 ++ .../com/muyu/common/domain/ApiFoxModule.java | 20 + .../java/com/muyu/common/domain/CPUInfo.java | 38 ++ .../muyu/common/domain/EcsInstanceInfo.java | 45 ++ .../java/com/muyu/common/domain/FlowInfo.java | 39 ++ .../java/com/muyu/common/domain/JVMInfo.java | 57 +++ .../java/com/muyu/common/domain/Result.java | 159 +++++++ .../muyu/common/domain/WorkGatewayNode.java | 25 + .../gateway/cache/GatewayLoadNodeCache.java | 59 +++ .../gateway/cache/GatewayLoadSeriesCache.java | 65 +++ .../gateway/cache/GatewayNodeCache.java | 53 +++ .../gateway/cache/GatewayNodeScoreCache.java | 9 + .../gateway/cache/GatewayNodeSetVinCache.java | 9 + .../cache/GatewayVehicleLineNodeCache.java | 9 + .../gateway/cache/abs/GatewayCacheAbs.java | 26 + .../gateway/module/GatewayNodeInfo.java | 29 ++ .../FastJson2JsonRedisSerializer.java | 51 ++ .../common/redis/configure/RedisConfig.java | 41 ++ .../common/redis/service/RedisService.java | 279 +++++++++++ .../java/com/muyu/common/utils/UserUtil.java | 52 ++ .../com/muyu/common/utils/uuid/IdUtils.java | 44 ++ .../java/com/muyu/common/utils/uuid/UUID.java | 447 ++++++++++++++++++ .../server/controller/GatewayController.java | 26 + .../server/service/GatewayLoadService.java | 14 + .../service/impl/GatewayLoadServiceImpl.java | 50 ++ src/main/resources/application.yml | 24 + src/test/java/com/muyu/LoadTest.java | 141 ++++++ 33 files changed, 2194 insertions(+) create mode 100644 .gitignore create mode 100644 src/main/java/com/muyu/LoadCenterApplication.java create mode 100644 src/main/java/com/muyu/common/aliyun/AliYunEcsService.java create mode 100644 src/main/java/com/muyu/common/aliyun/config/AliConfig.java create mode 100644 src/main/java/com/muyu/common/aliyun/config/InstanceConfig.java create mode 100644 src/main/java/com/muyu/common/aliyun/config/MqttInfoClient.java create mode 100644 src/main/java/com/muyu/common/aliyun/module/EcsSelectModule.java create mode 100644 src/main/java/com/muyu/common/domain/ApiFoxModule.java create mode 100644 src/main/java/com/muyu/common/domain/CPUInfo.java create mode 100644 src/main/java/com/muyu/common/domain/EcsInstanceInfo.java create mode 100644 src/main/java/com/muyu/common/domain/FlowInfo.java create mode 100644 src/main/java/com/muyu/common/domain/JVMInfo.java create mode 100644 src/main/java/com/muyu/common/domain/Result.java create mode 100644 src/main/java/com/muyu/common/domain/WorkGatewayNode.java create mode 100644 src/main/java/com/muyu/common/gateway/cache/GatewayLoadNodeCache.java create mode 100644 src/main/java/com/muyu/common/gateway/cache/GatewayLoadSeriesCache.java create mode 100644 src/main/java/com/muyu/common/gateway/cache/GatewayNodeCache.java create mode 100644 src/main/java/com/muyu/common/gateway/cache/GatewayNodeScoreCache.java create mode 100644 src/main/java/com/muyu/common/gateway/cache/GatewayNodeSetVinCache.java create mode 100644 src/main/java/com/muyu/common/gateway/cache/GatewayVehicleLineNodeCache.java create mode 100644 src/main/java/com/muyu/common/gateway/cache/abs/GatewayCacheAbs.java create mode 100644 src/main/java/com/muyu/common/gateway/module/GatewayNodeInfo.java create mode 100644 src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java create mode 100644 src/main/java/com/muyu/common/redis/configure/RedisConfig.java create mode 100644 src/main/java/com/muyu/common/redis/service/RedisService.java create mode 100644 src/main/java/com/muyu/common/utils/UserUtil.java create mode 100644 src/main/java/com/muyu/common/utils/uuid/IdUtils.java create mode 100644 src/main/java/com/muyu/common/utils/uuid/UUID.java create mode 100644 src/main/java/com/muyu/server/controller/GatewayController.java create mode 100644 src/main/java/com/muyu/server/service/GatewayLoadService.java create mode 100644 src/main/java/com/muyu/server/service/impl/GatewayLoadServiceImpl.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/com/muyu/LoadTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/src/main/java/com/muyu/LoadCenterApplication.java b/src/main/java/com/muyu/LoadCenterApplication.java new file mode 100644 index 0000000..2684937 --- /dev/null +++ b/src/main/java/com/muyu/LoadCenterApplication.java @@ -0,0 +1,18 @@ +package com.muyu; + +import org.springframework.boot.*; +import org.springframework.boot.autoconfigure.*; +import org.springframework.scheduling.annotation.*; + +/** + * 负载中心启动类 + * @author LiYongJie + * @date 2024/4/17 + */ +@SpringBootApplication +@EnableScheduling +public class LoadCenterApplication { + public static void main(String[] args) { + SpringApplication.run(LoadCenterApplication.class); + } +} diff --git a/src/main/java/com/muyu/common/aliyun/AliYunEcsService.java b/src/main/java/com/muyu/common/aliyun/AliYunEcsService.java new file mode 100644 index 0000000..767ebbd --- /dev/null +++ b/src/main/java/com/muyu/common/aliyun/AliYunEcsService.java @@ -0,0 +1,161 @@ +package com.muyu.common.aliyun; + +import com.aliyun.ecs20140526.*; +import com.aliyun.ecs20140526.models.*; +import com.aliyun.tea.*; +import com.aliyun.teautil.*; +import com.aliyun.teautil.models.*; +import com.muyu.common.aliyun.config.*; +import com.muyu.common.domain.*; +import com.muyu.common.utils.*; +import lombok.extern.slf4j.*; +import org.springframework.stereotype.*; + +import java.util.*; + +/** + * 阿里云ECS服务服务OpenAPI调用 + * @author LiYongJie + * @date 2024/4/17 + */ +@Slf4j +@Service +public class AliYunEcsService { + + /** + * 引入私有成员变量阿里云配置 + */ + private final AliConfig aliConfig; + + /** + * 引入私有成员变量实例配置 + */ + private final InstanceConfig instanceConfig; + + /** + * 引入私有成员变量客户端 + */ + private final Client client; + + public AliYunEcsService(AliConfig aliConfig, InstanceConfig instanceConfig, Client client){ + this.aliConfig = aliConfig; + this.instanceConfig = instanceConfig; + this.client = client; + } + + /** + * 通过备选实例规格创建ECS实例最佳实现 + */ + public String runInstances() throws Exception { + RunInstancesRequest request = new RunInstancesRequest() + .setRegionId(aliConfig.getRegionId()) + .setImageId(instanceConfig.getImageId()) + .setInstanceType(instanceConfig.getInstanceType()) + .setSecurityGroupId(instanceConfig.getSecurityGroupId()) + .setVSwitchId(instanceConfig.getVSwitchId()) + .setInstanceName("gateway:node:test") + .setDescription(new Date().toString() + "创建的实例节点") + .setInternetMaxBandwidthOut(Integer.valueOf(instanceConfig.getInternetMaxBandwidthOut())) + .setInternetChargeType(instanceConfig.getInternetChargeType()) + .setInstanceChargeType(instanceConfig.getInstanceChargeType()) + .setDryRun(false) + .setSystemDisk(new RunInstancesRequest.RunInstancesRequestSystemDisk() + .setSize(instanceConfig.getSize()) + .setCategory(instanceConfig.getCategory())); + + String result = ""; + try { + com.aliyun.teaconsole.Client.log("--------------------批量创建实例开始--------------------"); + RunInstancesResponse responces = client.runInstances(request); + com.aliyun.teaconsole.Client.log("--------------------创建实例成功,实例ID:" + com.aliyun.teautil.Common.toJSONString(responces.body.instanceIdSets.instanceIdSet) + "--------------------"); + //返回实例ID + result = responces.body.instanceIdSets.instanceIdSet + "";//前后带 [] + result = UserUtil.removeBrackets(result);//前后不带[] + } catch (TeaException error) { + com.aliyun.teaconsole.Client.log("--------------------创建实例失败:" + com.aliyun.teautil.Common.toJSONString(error.code) + "--------------------" + error.message); + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + com.aliyun.teaconsole.Client.log("--------------------创建实例失败:" + com.aliyun.teautil.Common.toJSONString(error.code) + "--------------------" + error.message); + } + return result; + } + + /** + * 传入实例ID + */ + public EcsInstanceInfo selectList(String instanceId) throws Exception{ + DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() + .setRegionId(aliConfig.getRegionId()) + .setInstanceName("*") + .setInstanceIds(Common.toJSONString(com.aliyun.darabonbastring.Client.split(instanceId, ",", 50))) + .setPageSize(10); + + // 初始化返回值 + List instanceList = null; + try { + DescribeInstancesResponse describeInstancesResponse = client.describeInstancesWithOptions(describeInstancesRequest, new RuntimeOptions()); + DescribeInstancesResponseBody body = describeInstancesResponse.getBody(); + DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstances instances = body.getInstances(); + + instanceList = instances.getInstance(); + + if (null != instanceList || instanceList.isEmpty()){ + return new EcsInstanceInfo(); + } + + EcsInstanceInfo ecsInstanceInfo = new EcsInstanceInfo(); + instanceList.forEach(item -> { + ecsInstanceInfo.setInstanceId(item.getInstanceId()); + ecsInstanceInfo.setInstanceName(item.getInstanceName()); + ecsInstanceInfo.setStatus(item.status); + ecsInstanceInfo.setPublicIpAddress(UserUtil.removeBrackets(item.getPublicIpAddress().getIpAddress().toString())); + ecsInstanceInfo.setRecyclable(item.getRecyclable()); + ecsInstanceInfo.setPrivateIpAddress(item.getVpcAttributes().getPrivateIpAddress().toString()); + }); + return ecsInstanceInfo; + } catch (TeaException e) { + e.printStackTrace(); + log.error("code:【{}】,message:【{}】,data:【{}】",e.code,e.message,e.data); + log.error(e.toString()); + log.error(e.getData().get("Recommend").toString()); + com.aliyun.teautil.Common.assertAsString(e.message); + }catch (Exception error){ + TeaException exception = new TeaException(error.getMessage(), error); + log.error("message:【{}】",error.getMessage(),error); + log.error(exception.message); + log.error(exception.getData().get("Recommend").toString()); + com.aliyun.teautil.Common.assertAsString(exception.message); + } + return new EcsInstanceInfo(); + } + + + /** + * 删除实例 + */ + public void deleteEcs(String instanceId) throws Exception{ + DeleteInstancesRequest deleteInstancesRequest = new DeleteInstancesRequest() + .setRegionId(aliConfig.getRegionId()) + .setDryRun(false) + .setForce(true) + .setTerminateSubscription(false) + .setInstanceId(Arrays.asList(instanceId)); + try { + // 复制代码运行请自行打印 API 的返回值 + client.deleteInstancesWithOptions(deleteInstancesRequest, new RuntimeOptions()); + } catch (TeaException error) { + // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // 错误 message + log.error("code:[{}], message: [{}],data: [{}]",error.getCode(),error.getMessage(),error.getData()); + System.out.println(error); + // 诊断地址 + System.out.println(error.getData().get("Recommend")); + com.aliyun.teautil.Common.assertAsString(error); + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // 错误 message + log.error("message: [{}]",_error.getMessage()); + } + } +} diff --git a/src/main/java/com/muyu/common/aliyun/config/AliConfig.java b/src/main/java/com/muyu/common/aliyun/config/AliConfig.java new file mode 100644 index 0000000..a61f9d6 --- /dev/null +++ b/src/main/java/com/muyu/common/aliyun/config/AliConfig.java @@ -0,0 +1,42 @@ +package com.muyu.common.aliyun.config; + +import com.aliyun.ecs20140526.*; +import com.aliyun.teaopenapi.models.*; +import lombok.*; +import org.springframework.boot.context.properties.*; +import org.springframework.context.annotation.*; + +import java.io.*; + +/** + * 阿里云配置 + * + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "config.ali") +public class AliConfig implements Serializable { + /** + * 用于标识用户 + */ + private String accessKeyId; + /** + * 用于用户验证的密钥 + */ + private String accessKeySecret; + /** + * 地域ID + */ + private String regionId; + + @Bean + public static Client createEcsClient(AliConfig aliConfig) throws Exception { + Config config = new Config() + .setAccessKeyId(aliConfig.getAccessKeyId()) + .setAccessKeySecret(aliConfig.getAccessKeySecret()) + .setRegionId(aliConfig.getRegionId()); + return new Client(config); + } +} diff --git a/src/main/java/com/muyu/common/aliyun/config/InstanceConfig.java b/src/main/java/com/muyu/common/aliyun/config/InstanceConfig.java new file mode 100644 index 0000000..5c12c1e --- /dev/null +++ b/src/main/java/com/muyu/common/aliyun/config/InstanceConfig.java @@ -0,0 +1,59 @@ +package com.muyu.common.aliyun.config; + +import lombok.*; +import org.springframework.boot.context.properties.*; +import org.springframework.context.annotation.*; + +import java.io.*; + +/** + * 创建实例的参数映射yml + * + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "config.instance") +public class InstanceConfig implements Serializable { + /** + * 地域ID + */ + private String regionId; + /** + * 镜像ID + */ + private String imageId; + /** + * 实例规格 + */ + private String instanceType; + /** + * 安全组ID + */ + private String securityGroupId; + /** + * 虚拟交换机ID + */ + private String vSwitchId; + /** + * 公网出带宽最大值,单位Mbit/s, 取值范围:1~100,默认值0 + */ + private String internetMaxBandwidthOut; + /** + * 系统盘大小 + */ + private String size; + /** + * 系统云盘类型 + */ + private String category; + /** + * ECS实例计费方式 + */ + private String instanceChargeType; + /** + * 网络计费类型 + */ + private String internetChargeType; +} diff --git a/src/main/java/com/muyu/common/aliyun/config/MqttInfoClient.java b/src/main/java/com/muyu/common/aliyun/config/MqttInfoClient.java new file mode 100644 index 0000000..f8dbdd3 --- /dev/null +++ b/src/main/java/com/muyu/common/aliyun/config/MqttInfoClient.java @@ -0,0 +1,38 @@ +package com.muyu.common.aliyun.config; + +import com.alibaba.fastjson2.*; +import lombok.extern.slf4j.*; +import okhttp3.*; + +/** + * mqtt客户端 + * @author liyongjie + * @date 2024/4/17 + */ +@Slf4j +public class MqttInfoClient { + public static final String URL = "http://10.10.26.5:8080/public/cluster"; + + public static void main(String[] args) { + OkHttpClient client = new OkHttpClient(); + + Request request = new Request.Builder() + .url(URL) + .get() + .addHeader("User-Agent", "Apifox/1.0.0(https://apifox.com)") + .addHeader("Accesstoken", "") + .build(); + + try { + Response response = client.newCall(request).execute(); + + JSONArray jsonArray = JSONArray.parse(response.body().string()); + JSONObject jsonObject = jsonArray.getJSONObject(0); + JSONObject mqttInfo = jsonObject.getJSONObject("mqttInfo"); + int connectSize = mqttInfo.getIntValue("connectSize"); + log.info(connectSize + ""); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/muyu/common/aliyun/module/EcsSelectModule.java b/src/main/java/com/muyu/common/aliyun/module/EcsSelectModule.java new file mode 100644 index 0000000..82f2641 --- /dev/null +++ b/src/main/java/com/muyu/common/aliyun/module/EcsSelectModule.java @@ -0,0 +1,27 @@ +package com.muyu.common.aliyun.module; + +import lombok.*; + +import java.io.*; +import java.util.*; + +/** + * ECS查询模型 + * + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EcsSelectModule implements Serializable { + /** + * 实例ID + */ + private List instanceIdList; + /** + * 实例名称 + */ + private List instanceNameList; +} diff --git a/src/main/java/com/muyu/common/domain/ApiFoxModule.java b/src/main/java/com/muyu/common/domain/ApiFoxModule.java new file mode 100644 index 0000000..3a2c395 --- /dev/null +++ b/src/main/java/com/muyu/common/domain/ApiFoxModule.java @@ -0,0 +1,20 @@ +package com.muyu.common.domain; + +import lombok.*; + +/** + * 节点信息 + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +public class ApiFoxModule { + /** + * 节点ID + */ + private String clusterId; + /* + * CPU使用信息 + */ + private CPUInfo cupInfo; +} diff --git a/src/main/java/com/muyu/common/domain/CPUInfo.java b/src/main/java/com/muyu/common/domain/CPUInfo.java new file mode 100644 index 0000000..e04f3dc --- /dev/null +++ b/src/main/java/com/muyu/common/domain/CPUInfo.java @@ -0,0 +1,38 @@ +package com.muyu.common.domain; + +import lombok.*; + +import java.io.*; + +/** + * CPU使用信息 + * + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CPUInfo implements Serializable { + /** + * CPU核数 + */ + private long cpuNum; + /** + * 内核态使用率 + */ + private String cSys; + /** + * 空闲率 + */ + private String idle; + /** + * I/O等待 + */ + private String iowait; + /** + * 用户态使用率 + */ + private String user; +} diff --git a/src/main/java/com/muyu/common/domain/EcsInstanceInfo.java b/src/main/java/com/muyu/common/domain/EcsInstanceInfo.java new file mode 100644 index 0000000..06f8501 --- /dev/null +++ b/src/main/java/com/muyu/common/domain/EcsInstanceInfo.java @@ -0,0 +1,45 @@ +package com.muyu.common.domain; + +import lombok.*; + +import java.io.*; + +/** + * 实例查询结果模型 + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EcsInstanceInfo implements Serializable { + /** + * 实例ID + */ + private String instanceId; + /** + * 实例名称 + */ + private String instanceName; + /** + * 实例状态 + */ + private String status; + /** + * 公网IP + */ + private String publicIpAddress; + /** + * 私网IP + */ + private String privateIpAddress; + /** + * 创建时间 + */ + private String creationTime; + /** + * 是否可回收 + */ + private boolean recyclable; +} diff --git a/src/main/java/com/muyu/common/domain/FlowInfo.java b/src/main/java/com/muyu/common/domain/FlowInfo.java new file mode 100644 index 0000000..b4c6f07 --- /dev/null +++ b/src/main/java/com/muyu/common/domain/FlowInfo.java @@ -0,0 +1,39 @@ +package com.muyu.common.domain; + +import lombok.*; + +import java.io.*; + +/** + * 节点状态 + * + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FlowInfo implements Serializable { + /** + * 上次读取吞吐量 + */ + private String lastReadThroughput; + /** + * 上次写入吞吐量 + */ + private String lastWriteThroughput; + /** + * 读取总吞吐量 + */ + private String readByBytesHistory; + /** + * 实写字节 + */ + private String readBytes; + /** + * 写入总吞吐量 + */ + private String writeBytesHistory; + +} diff --git a/src/main/java/com/muyu/common/domain/JVMInfo.java b/src/main/java/com/muyu/common/domain/JVMInfo.java new file mode 100644 index 0000000..1967d9a --- /dev/null +++ b/src/main/java/com/muyu/common/domain/JVMInfo.java @@ -0,0 +1,57 @@ +package com.muyu.common.domain; + +import lombok.*; + +import java.io.*; + +/** + * JVM使用信息 + * @author LiYongJie + * @date 2024/4/17 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class JVMInfo implements Serializable { + /** + * 文件描述(句柄) + */ + private String fileDescriptors; + /** + * 堆内存 + */ + private String heapCommit; + /** + * 堆初始化空间 + */ + private String heapInit; + /** + * 堆最大内存 + */ + private String heapMax; + /** + * 堆使用空间 + */ + private String heapUsed; + /** + * Java目录 + */ + private String jdkHome; + /** + * jdk版本 + */ + private String jdkVersion; + /** + * 非堆初始化空间 + */ + private String noHeapInit; + /** + * 非堆最大使用空间 + */ + private String noHeapUsed; + /** + * 线程数量 + */ + private long threadCount; +} diff --git a/src/main/java/com/muyu/common/domain/Result.java b/src/main/java/com/muyu/common/domain/Result.java new file mode 100644 index 0000000..2e48fe2 --- /dev/null +++ b/src/main/java/com/muyu/common/domain/Result.java @@ -0,0 +1,159 @@ +package com.muyu.common.domain; + +import lombok.*; + +import java.io.*; + +/** + * 响应信息主体 + * + * @author LiYongJie + * @date 2024/4/18 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Result implements Serializable { + /** + * 成功 + */ + public static final int SUCCESS = 200; + /** + * 失败 + */ + public static final int FAIL = 500; + /** + * 警告 + */ + private static final int WARN = 501; + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 1L; + /** + * 状态码 + */ + private int code; + /** + * 响应信息 + */ + private String msg; + /** + * 泛型数据 + */ + private T data; + + public static Result buildCode(int code, String msg, T data) { + return restResult(data, code, msg); + } + + public static Result success() { + return restResult(null, SUCCESS, null); + } + + public static Result success(T data) { + return restResult(data, SUCCESS, null); + } + + public static Result success(T data, String msg) { + return restResult(data, SUCCESS, msg); + } + + public static Result error() { + return restResult(null, FAIL, null); + } + + public static Result error(T data) { + return restResult(data, FAIL, null); + } + + public static Result error(String msg) { + return restResult(null, FAIL, msg); + } + + public static Result error(T data, String msg) { + return restResult(data, FAIL, msg); + } + + public static Result error(int code, String msg) { + return restResult(null, code, msg); + } + + public static Result warn() { + return restResult(null, WARN, null); + } + + public static Result warn(String msg) { + return restResult(null, WARN, msg); + } + + public static Result warn(T data) { + return restResult(data, WARN, null); + } + + public static Result warn(T data, String msg) { + return restResult(null, WARN, msg); + } + + public static Result warn(int code, String msg) { + return restResult(null, code, msg); + } + + + private static Result restResult(T data, int code, String msg) { + return Result.builder() + .code(code) + .data(data) + .msg(msg) + .build(); + } + + public static Boolean isError(Result ret) { + return !isSuccess(ret); + } + + private static boolean isSuccess(Result ret) { + return Result.SUCCESS == ret.getCode(); + } + + /** + * Mqtt事件信息 + * @author LiYongJie + * @date 2024/4/18 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class MqttInfo implements Serializable { + /** + * 关闭事件数量 + */ + private long closeEventSize; + /** + * 连接事件数量 + */ + private long connectEventSize; + /** + * 连接总数 + */ + private long connectSize; + /** + * 断开连接数量 + */ + private long disconnectEventSize; + /** + * 推送数量 + */ + private long publishEventSize; + /** + * 发布重试事件数量 + */ + private long publishRetryEventSize; + /** + * 保留消息数量 + */ + private long retainSize; + } +} diff --git a/src/main/java/com/muyu/common/domain/WorkGatewayNode.java b/src/main/java/com/muyu/common/domain/WorkGatewayNode.java new file mode 100644 index 0000000..c9917ee --- /dev/null +++ b/src/main/java/com/muyu/common/domain/WorkGatewayNode.java @@ -0,0 +1,25 @@ +package com.muyu.common.domain; + +import lombok.*; + +import java.io.*; + +/** + * 网关节点 + * @author LiYongJie + * @date 2024/4/18 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkGatewayNode implements Serializable { + /** + * 节点IP + */ + private String nodeId; + /** + * 权重 + */ + private String weight; +} diff --git a/src/main/java/com/muyu/common/gateway/cache/GatewayLoadNodeCache.java b/src/main/java/com/muyu/common/gateway/cache/GatewayLoadNodeCache.java new file mode 100644 index 0000000..133f5d5 --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/cache/GatewayLoadNodeCache.java @@ -0,0 +1,59 @@ +package com.muyu.common.gateway.cache; + +import com.muyu.common.gateway.cache.abs.*; +import com.muyu.common.redis.service.*; +import org.springframework.beans.factory.annotation.*; +import org.springframework.stereotype.*; + +import java.util.*; + +/** + * 网关负载节点缓存 + * @author LiYongJie + * @date 2024/4/18 + */ +@Component +public class GatewayLoadNodeCache extends GatewayCacheAbs { + + @Autowired + private RedisService redisService; + + /** + * 网关负载节点缓存key + */ + public static final String gatewayLoadNodeKey = "node"; + + @Override + public String getPre(){ + return "gateway:load:"; + } + + /** + * 存负载集合 + * @param nodeList 节点权重集合 + */ + public void put(List nodeList){ + redisService.deleteObject(enCode(gatewayLoadNodeKey)); + redisService.setCacheList(enCode(gatewayLoadNodeKey), nodeList); + } + + /** + * 获取所有负载节点 + * @return 负载节点集合 + */ + public List get(){ + return redisService.getCacheList(enCode(gatewayLoadNodeKey)); + } + + /** + * 通过下标获取节点 + * @param index 下标 + * @return 指定节点 + */ + public String getByIndex(Long index){ + if (null == index || index > 100){ + throw new RuntimeException("下标越界,0~100"); + } + return redisService.getCacheListValue(enCode(gatewayLoadNodeKey), index); + } +} diff --git a/src/main/java/com/muyu/common/gateway/cache/GatewayLoadSeriesCache.java b/src/main/java/com/muyu/common/gateway/cache/GatewayLoadSeriesCache.java new file mode 100644 index 0000000..bdcbf66 --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/cache/GatewayLoadSeriesCache.java @@ -0,0 +1,65 @@ +package com.muyu.common.gateway.cache; + +import com.muyu.common.gateway.cache.abs.GatewayCacheAbs; +import com.muyu.common.redis.service.*; +import lombok.extern.java.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.*; + +import javax.annotation.*; + +/** + * 网关负载序列 + * @author LiYongJie + * @date 2024/4/18 + */ +@Component +public class GatewayLoadSeriesCache extends GatewayCacheAbs { + + /** + * 注入redis工具类 + */ + @Autowired + private RedisService redisService; + + /** + * 网关负载序列后缀 + */ + public static final String gatewayLoadSeriesKey = "series"; + + @Override + public String getPre() { + return "gateway:load:"; + } + + /** + * bean创建完成之后执行方法 + */ + @PostConstruct + public void init(){ + redisService.setCacheObject(enCode(gatewayLoadSeriesKey), 0L); + } + + /** + * 获取当前序列值 + * @return 序列值 + */ + public Long get(){ + return redisService.getCacheObject(enCode(gatewayLoadSeriesKey)); + } + + /** + * 获取自增序列值 + * @return 自增后的值 + */ + public Long incrementAndGet(){ + return redisService.increment(enCode(gatewayLoadSeriesKey), 1L); + } + + /** + * 重置 + */ + public void reset(){ + this.init(); + } +} diff --git a/src/main/java/com/muyu/common/gateway/cache/GatewayNodeCache.java b/src/main/java/com/muyu/common/gateway/cache/GatewayNodeCache.java new file mode 100644 index 0000000..ee23be7 --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/cache/GatewayNodeCache.java @@ -0,0 +1,53 @@ +package com.muyu.common.gateway.cache; + +import com.muyu.common.gateway.cache.abs.GatewayCacheAbs; +import com.muyu.common.gateway.module.*; +import com.muyu.common.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.*; + +/** + * 网关节点缓存 + * @author LiYongJie + * @date 2024/4/18 + */ +@Component +public class GatewayNodeCache extends GatewayCacheAbs { + + /** + * 注入redis工具类 + */ + @Autowired + private RedisService redisService; + + @Override + public String getPre(){ + return "gateway:node:info"; + } + + /** + * 增加缓存数据 + * @param gatewayNodeInfo 节点信息 + */ + public void put(GatewayNodeInfo gatewayNodeInfo){ + redisService.setCacheObject(enCode(gatewayNodeInfo.getNodeId()), gatewayNodeInfo); + } + + /** + * 获取缓存数据 + * @param nodeId 节点ID + * @return 节点信息 + */ + public GatewayNodeInfo get(String nodeId){ + return redisService.getCacheObject(enCode(nodeId)); + } + + + /** + * 删除网关节点 + * @param nodeId 节点ID + */ + public void remove(String nodeId){ + redisService.deleteObject(enCode(nodeId)); + } +} diff --git a/src/main/java/com/muyu/common/gateway/cache/GatewayNodeScoreCache.java b/src/main/java/com/muyu/common/gateway/cache/GatewayNodeScoreCache.java new file mode 100644 index 0000000..bdae5ea --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/cache/GatewayNodeScoreCache.java @@ -0,0 +1,9 @@ +package com.muyu.common.gateway.cache; + +/** + * 网关节点分数 + * @author LiYongJie + * @date 2024/4/18 + */ +public class GatewayNodeScoreCache { +} diff --git a/src/main/java/com/muyu/common/gateway/cache/GatewayNodeSetVinCache.java b/src/main/java/com/muyu/common/gateway/cache/GatewayNodeSetVinCache.java new file mode 100644 index 0000000..6e07254 --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/cache/GatewayNodeSetVinCache.java @@ -0,0 +1,9 @@ +package com.muyu.common.gateway.cache; + +/** + * 网关节点存储VIN详情 + * @author LiYongJie + * @date 2024/4/18 + */ +public class GatewayNodeSetVinCache { +} diff --git a/src/main/java/com/muyu/common/gateway/cache/GatewayVehicleLineNodeCache.java b/src/main/java/com/muyu/common/gateway/cache/GatewayVehicleLineNodeCache.java new file mode 100644 index 0000000..8ce0f17 --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/cache/GatewayVehicleLineNodeCache.java @@ -0,0 +1,9 @@ +package com.muyu.common.gateway.cache; + +/** + * 网关车辆对应网关节点 + * @author LiYongJie + * @date 2024/4/18 + */ +public class GatewayVehicleLineNodeCache { +} diff --git a/src/main/java/com/muyu/common/gateway/cache/abs/GatewayCacheAbs.java b/src/main/java/com/muyu/common/gateway/cache/abs/GatewayCacheAbs.java new file mode 100644 index 0000000..24dd036 --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/cache/abs/GatewayCacheAbs.java @@ -0,0 +1,26 @@ +package com.muyu.common.gateway.cache.abs; + +import com.muyu.common.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.*; + +/** + * 缓存抽象类 + * @author LiYongJie + * @date 2024/4/18 + */ +public abstract class GatewayCacheAbs { + + /** + * 注入redis工具类 + */ + @Autowired + private RedisService redisService; + + public abstract String getPre(); + + public String enCode(K key){ + return getPre() + key; + } +} diff --git a/src/main/java/com/muyu/common/gateway/module/GatewayNodeInfo.java b/src/main/java/com/muyu/common/gateway/module/GatewayNodeInfo.java new file mode 100644 index 0000000..e04401e --- /dev/null +++ b/src/main/java/com/muyu/common/gateway/module/GatewayNodeInfo.java @@ -0,0 +1,29 @@ +package com.muyu.common.gateway.module; + +import lombok.*; + +import java.io.*; + +/** + * 网关节点信息 + * @author LiYongJie + * @date 2024/4/18 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class GatewayNodeInfo implements Serializable { + /** + * 节点ID + */ + private String nodeId; + /** + * 公网IP + */ + private String publicIdAddress; + /** + * 内网IP + */ + private String privateIdAddress; +} diff --git a/src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java b/src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..2cb858b --- /dev/null +++ b/src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java @@ -0,0 +1,51 @@ +package com.muyu.common.redis.configure; + +import com.alibaba.fastjson2.*; +import com.alibaba.fastjson2.filter.*; +import org.springframework.data.redis.serializer.*; + +import java.nio.charset.*; + +/** + * Redis使用FastJson序列化 + * + * @author muyu + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.muyu" }; + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter( JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/src/main/java/com/muyu/common/redis/configure/RedisConfig.java b/src/main/java/com/muyu/common/redis/configure/RedisConfig.java new file mode 100644 index 0000000..1574c73 --- /dev/null +++ b/src/main/java/com/muyu/common/redis/configure/RedisConfig.java @@ -0,0 +1,41 @@ +package com.muyu.common.redis.configure; + +import org.springframework.boot.autoconfigure.*; +import org.springframework.boot.autoconfigure.data.redis.*; +import org.springframework.cache.annotation.*; +import org.springframework.context.annotation.*; +import org.springframework.data.redis.connection.*; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.serializer.*; + +/** + * redis配置 + * + * @author muyu + */ +@Configuration +@EnableCaching +@AutoConfigureBefore(RedisAutoConfiguration.class) +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/com/muyu/common/redis/service/RedisService.java b/src/main/java/com/muyu/common/redis/service/RedisService.java new file mode 100644 index 0000000..b80ac58 --- /dev/null +++ b/src/main/java/com/muyu/common/redis/service/RedisService.java @@ -0,0 +1,279 @@ +package com.muyu.common.redis.service; + +import org.springframework.beans.factory.annotation.*; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.*; + +import java.io.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * Spring redis 工具类 + * + * @author muyu + * @date 2024/4/18 + */ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisService implements Serializable { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + public T getCacheListValue(final String key, long index) { + return (T) redisTemplate.opsForList().index(key, index); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 判断redis中hashKey是否存在 + * + * @param key redis键 + * @param hashKey hash键 + */ + public boolean hashKey(final String key, final String hashKey) { + return this.redisTemplate.opsForHash().hasKey(key, hashKey); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 减少序列值 + * + * @param key key + * @param number 值 + * @return 操作后的值 + */ + public Long decrement(final String key, Long number) { + return redisTemplate.opsForValue().decrement(key, number); + } + + /** + * 增加序列值 + * + * @param key key + * @param number 值 + * @return 操作后的值 + */ + public Long increment(final String key, Long number) { + return redisTemplate.opsForValue().increment(key, number); + } +} diff --git a/src/main/java/com/muyu/common/utils/UserUtil.java b/src/main/java/com/muyu/common/utils/UserUtil.java new file mode 100644 index 0000000..8e42010 --- /dev/null +++ b/src/main/java/com/muyu/common/utils/UserUtil.java @@ -0,0 +1,52 @@ +package com.muyu.common.utils; + + +import com.muyu.common.aliyun.utils.uuid.*; +import com.muyu.common.redis.service.*; +import org.springframework.beans.factory.annotation.*; +import org.springframework.stereotype.*; + +import java.util.concurrent.*; + +/** + * 自定义工具类 + * @Author: LiuYunHu + * @CreateTime: 2024/4/13 + */ +@Component +public class UserUtil { + @Autowired + private RedisService redisService; + + /* + * @Author: LiuYunHu + * @Date: 2024/4/13 9:25 + * @Description: 将字符串前后的[]进行删除 + * @Param: [input] + * @Return: java.lang.String + **/ + public static String removeBrackets(String input) { + // 使用正则表达式匹配并移除括号 + String pattern = "\\[|\\]"; // 匹配 [ 或 ] + return input.replaceAll(pattern, ""); + } + + + /* + * @Author: LiuYunHu + * @Date: 2024/4/13 11:42 + * @Description: 获取实例注册token令牌 + * @Param: + * @Return: + **/ + public String getToken(String instanceIp) { + //生成令牌 + String randomUUID = IdUtils.randomUUID(); + + //缓存两分钟 + redisService.setCacheObject("lyhToken:" + instanceIp, randomUUID, 2L, TimeUnit.MINUTES); + + //令牌返回 + return randomUUID; + } +} diff --git a/src/main/java/com/muyu/common/utils/uuid/IdUtils.java b/src/main/java/com/muyu/common/utils/uuid/IdUtils.java new file mode 100644 index 0000000..1bcf330 --- /dev/null +++ b/src/main/java/com/muyu/common/utils/uuid/IdUtils.java @@ -0,0 +1,44 @@ +package com.muyu.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author couplet + */ +public class IdUtils { + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID () { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID () { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID () { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID () { + return UUID.fastUUID().toString(true); + } +} diff --git a/src/main/java/com/muyu/common/utils/uuid/UUID.java b/src/main/java/com/muyu/common/utils/uuid/UUID.java new file mode 100644 index 0000000..aea51e7 --- /dev/null +++ b/src/main/java/com/muyu/common/utils/uuid/UUID.java @@ -0,0 +1,447 @@ +package com.muyu.common.utils.uuid; + + +import java.security.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author couplet + */ +public final class UUID implements java.io.Serializable, Comparable { + private static final long serialVersionUID = -1185015143654744140L; + /** + * 此UUID的最高64有效位 + */ + private final long mostSigBits; + /** + * 此UUID的最低64有效位 + */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID (byte[] data) { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0 ; i < 8 ; i++) { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8 ; i < 16 ; i++) { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID (long mostSigBits, long leastSigBits) { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID () { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID () { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID (boolean isSecure) { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes (byte[] name) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException nsae) { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * + * @return 具有指定值的 {@code UUID} + * + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + */ + public static UUID fromString (String name) { + String[] components = name.split("-"); + if (components.length != 5) { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0 ; i < 5 ; i++) { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * + * @return 值 + */ + private static String digits (long val, int digits) { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom () { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom () { + return ThreadLocalRandom.current(); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits () { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits () { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version () { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant () { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp () throws UnsupportedOperationException { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence () throws UnsupportedOperationException { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node () throws UnsupportedOperationException { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * + * @see #toString(boolean) + */ + @Override + public String toString () { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString (boolean isSimple) { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (false == isSimple) { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (false == isSimple) { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (false == isSimple) { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (false == isSimple) { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + // Comparison Operations + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode () { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals (Object obj) { + if ((null == obj) || (obj.getClass() != UUID.class)) { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + */ + @Override + public int compareTo (UUID val) { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase () { + if (version() != 1) { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * SecureRandom 的单例 + */ + private static class Holder { + static final SecureRandom numberGenerator = getSecureRandom(); + } +} diff --git a/src/main/java/com/muyu/server/controller/GatewayController.java b/src/main/java/com/muyu/server/controller/GatewayController.java new file mode 100644 index 0000000..98034cf --- /dev/null +++ b/src/main/java/com/muyu/server/controller/GatewayController.java @@ -0,0 +1,26 @@ +package com.muyu.server.controller; + +import com.muyu.common.domain.*; +import com.muyu.server.service.*; +import org.springframework.beans.factory.annotation.*; +import org.springframework.web.bind.annotation.*; + +/** + * 网关控制层 + * @author LiYongJie + * @date 2024/4/18 + */ +@RestController +@RequestMapping("/gateway") +public class GatewayController { + /** + * 注入网关负载服务 + */ + @Autowired + private GatewayLoadService gatewayLoadService; + + @GetMapping("/load/node") + public Result loadNode(){ + return Result.success(gatewayLoadService.loadNode()); + } +} diff --git a/src/main/java/com/muyu/server/service/GatewayLoadService.java b/src/main/java/com/muyu/server/service/GatewayLoadService.java new file mode 100644 index 0000000..887ef6d --- /dev/null +++ b/src/main/java/com/muyu/server/service/GatewayLoadService.java @@ -0,0 +1,14 @@ +package com.muyu.server.service; + +/** + * 网关负载业务 + * @Author LiYongJie + * @Date 2024/4/18 + */ +public interface GatewayLoadService { + /** + * 负载网关节点 + * @return + */ + String loadNode(); +} diff --git a/src/main/java/com/muyu/server/service/impl/GatewayLoadServiceImpl.java b/src/main/java/com/muyu/server/service/impl/GatewayLoadServiceImpl.java new file mode 100644 index 0000000..bb28517 --- /dev/null +++ b/src/main/java/com/muyu/server/service/impl/GatewayLoadServiceImpl.java @@ -0,0 +1,50 @@ +package com.muyu.server.service.impl; + +import com.muyu.common.gateway.cache.*; +import com.muyu.common.gateway.module.*; +import com.muyu.server.service.*; +import lombok.*; +import org.springframework.stereotype.*; + +/** + * 负载实现层 + * @author LiYongJie + * @date 2024/4/18 + */ +@Service +@AllArgsConstructor +public class GatewayLoadServiceImpl implements GatewayLoadService { + + /** + * 节点长度 + */ + public static final Long nodeLength = 100L; + + /** + * 负载信息 + */ + private final GatewayLoadNodeCache gatewayLoadNodeCache; + + /** + * 负载序列 + */ + private final GatewayLoadSeriesCache gatewayLoadSeriesCache; + + /** + * 节点信息 + */ + private final GatewayNodeCache gatewayNodeCache; + + /** + * 负载节点 + * @return 返回负载节点 + */ + @Override + public String loadNode() { + Long seriesLoad = gatewayLoadSeriesCache.incrementAndGet(); + Long seriesLoadIndex = seriesLoad % nodeLength; + String loadNodeId = gatewayLoadNodeCache.getByIndex(seriesLoadIndex); + GatewayNodeInfo gatewayNodeInfo = gatewayNodeCache.get(loadNodeId); + return gatewayNodeInfo.getPublicIdAddress(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..1e35224 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,24 @@ +# Tomcat +server: + port: 9010 +Spring: + redis: + host: 120.27.163.37 + port: 6379 + password: Lzk750110.ylyyds! + +config: + ali: + access-key-id: LTAI5tANGefs2gi8nsu4AoSZ + access-key-secret: Ut5RaJvvG7dP8hgK82qjdtvyUA6x8g + region-id: cn-zhangjiakou + instance: + image-id: m-8vb2cpmwwggxp598kjue + instance-type: ecs.g6.large + security-group-id: sg-8vbamw309ei7fz4vucfb + internet-max-bandwidth-out: 5 + size: 20 + category: cloud_ssd + instance-charge-type: PostPaid + internet-charge-type: PayByTraffic + diff --git a/src/test/java/com/muyu/LoadTest.java b/src/test/java/com/muyu/LoadTest.java new file mode 100644 index 0000000..c5616f0 --- /dev/null +++ b/src/test/java/com/muyu/LoadTest.java @@ -0,0 +1,141 @@ +package com.muyu; + +import com.muyu.common.redis.service.*; +import lombok.*; +import lombok.extern.slf4j.*; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.*; +import org.springframework.boot.test.context.*; + +import java.util.*; +import java.util.concurrent.*; + +/** + * 负载测试 + * + * @author LiYongJie + * @date 2024/4/18 + */ +@Slf4j +@SpringBootTest(classes = LoadCenterApplication.class) +public class LoadTest { + + /** + * 注入redis服务 + */ + @Autowired + private RedisService redisService; + + @Test + public void load() { + // 初始化序列 + redisService.setCacheObject("cursor", 0); + List nodeIdList = new ArrayList<>() {{ + add(new WorkGatewayNode("work-gateway-node-A", 8)); + add(new WorkGatewayNode("work-gateway-node-B", 12)); + add(new WorkGatewayNode("work-gateway-node-C", 2)); + add(new WorkGatewayNode("work-gateway-node-D", 39)); + add(new WorkGatewayNode("work-gateway-node-E", 39)); + }}; + + // 权重100% + List loadNodeList = new ArrayList<>(); + + long count = nodeIdList.stream().mapToInt(WorkGatewayNode::getWeight).sum(); + + if (count < 100) { + List list = nodeIdList.stream() + .sorted((o1, o2) -> o2.getWeight() - o1.getWeight()) + .toList(); + int countWeight = 0; + for (long i = count; i < 100; i++) { + WorkGatewayNode workGatewayNode = list.get(countWeight++ % list.size()); + workGatewayNode.setWeight(workGatewayNode.getWeight() + 1); + } + } + whFor: + while (true) { + for (WorkGatewayNode workGatewayNode : nodeIdList) { + Integer weight = workGatewayNode.getWeight(); + if (weight > 0) { + loadNodeList.add(workGatewayNode.getNodeId()); + } + workGatewayNode.setWeight(weight - 1); + } + int sum = nodeIdList.stream() + .mapToInt(WorkGatewayNode::getWeight) + .sum(); + if (sum <= 0) { + break whFor; + } + redisService.deleteObject("work:node:gateway"); + redisService.setCacheList("work:node:gateway", loadNodeList); + CountDownLatch countDownLatch = new CountDownLatch(3000); + + new Thread(() -> { + for (int i = 0; i < 1000; i++) { + Long cursor = redisService.increment("cursor", 1L); + String nodeId = redisService.getCacheListValue("work:node:gateway", cursor % 100); + System.out.println(Thread.currentThread().getName() + ":" + cursor + "-----" + nodeId); + stiNode.sti(nodeId); + countDownLatch.countDown(); + } + }).start(); + new Thread(() -> { + for (int i = 0; i < 1000; i++) { + Long cursor = redisService.increment("cursor", 1L); + String nodeId = redisService.getCacheListValue("work:node:gateway", cursor % 100); + System.out.println(Thread.currentThread().getName() + ":" + cursor + "-----" + nodeId); + stiNode.sti(nodeId); + countDownLatch.countDown(); + } + }).start(); + new Thread(() -> { + for (int i = 0; i < 1000; i++) { + Long cursor = redisService.increment("cursor", 1L); + String nodeId = redisService.getCacheListValue("work:node:gateway", cursor % 100); + System.out.println(Thread.currentThread().getName() + ":" + cursor + "-----" + nodeId); + stiNode.sti(nodeId); + countDownLatch.countDown(); + } + }).start(); + try { + countDownLatch.await(); + stiNode.show(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + class stiNode { + private static Map stiNodeMap = new HashMap<>(); + + public synchronized static void sti(String nodeId) { + Integer stiCount = stiNodeMap.getOrDefault(nodeId, 0); + stiNodeMap.put(nodeId, stiCount + 1); + } + + public static void show() { + stiNodeMap.forEach((key, val) -> { + System.out.println(key + "-----" + val); + }); + } + } +} + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +class WorkGatewayNode { + /** + * 节点ID + */ + private String nodeId; + /** + * 权重 + */ + private Integer weight; +}