From ddef239905ce0c088bf731a9d0b97362d44f372b Mon Sep 17 00:00:00 2001 From: 20300 <643145201@qq.com> Date: Thu, 6 Jun 2024 22:31:22 +0800 Subject: [PATCH] =?UTF-8?q?feat()=E8=A7=A3=E6=9E=90=E6=8A=A5=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 14 + .../hyc/controller/SummarizeController.java | 45 + .../demo/config/KafkaConsumerConfig.java | 112 +++ .../demo/config/KafkaProviderConfig.java | 90 ++ .../demo/config/KafkaSendResultHandler.java | 25 + .../config/MyKafkaListenerErrorHandler.java | 34 + .../kafka/demo/consumer/KafkaConsumer.java | 31 + .../hyc/producer/TransmitMessageProducer.java | 12 + .../com/hyc/util/CalculateCheckDigit.java | 44 + .../java/com/hyc/util/ConversionUtil.java | 65 ++ src/main/java/com/hyc/util/Convert.java | 893 ++++++++++++++++++ src/main/java/com/hyc/util/MD5Util.java | 70 ++ src/main/java/com/hyc/util/ReflectUtils.java | 314 ++++++ src/main/resources/application.yml | 71 ++ 14 files changed, 1820 insertions(+) create mode 100644 src/main/java/com/hyc/kafka/demo/config/KafkaConsumerConfig.java create mode 100644 src/main/java/com/hyc/kafka/demo/config/KafkaProviderConfig.java create mode 100644 src/main/java/com/hyc/kafka/demo/config/KafkaSendResultHandler.java create mode 100644 src/main/java/com/hyc/kafka/demo/config/MyKafkaListenerErrorHandler.java create mode 100644 src/main/java/com/hyc/kafka/demo/consumer/KafkaConsumer.java create mode 100644 src/main/java/com/hyc/producer/TransmitMessageProducer.java create mode 100644 src/main/java/com/hyc/util/CalculateCheckDigit.java create mode 100644 src/main/java/com/hyc/util/ConversionUtil.java create mode 100644 src/main/java/com/hyc/util/Convert.java create mode 100644 src/main/java/com/hyc/util/MD5Util.java create mode 100644 src/main/java/com/hyc/util/ReflectUtils.java diff --git a/pom.xml b/pom.xml index 855a155..51b91d4 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,20 @@ 8.0.11 runtime + + + + + + + + + org.springframework.kafka + spring-kafka + 2.8.0 + + + org.mybatis.spring.boot diff --git a/src/main/java/com/hyc/controller/SummarizeController.java b/src/main/java/com/hyc/controller/SummarizeController.java index 8c3c221..1bfef50 100644 --- a/src/main/java/com/hyc/controller/SummarizeController.java +++ b/src/main/java/com/hyc/controller/SummarizeController.java @@ -1,15 +1,24 @@ package com.hyc.controller; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.hyc.domain.ConnectionParameter; import com.hyc.domain.SummarizeResp; import com.hyc.result.Result; import com.hyc.service.SummarizeService; +import com.hyc.util.CalculateCheckDigit; +import com.hyc.util.ConversionUtil; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.*; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; /** @@ -80,6 +89,41 @@ public class SummarizeController { System.out.println("topic: " + topic); System.out.println("Qos: " + message.getQos()); System.out.println("message content: " + new String(message.getPayload())); + String resultString = ConversionUtil.hexStringToString(new String(message.getPayload())); + log.warn("解析后的字符串是:{}",resultString); + log.warn("长度为:{}",resultString.length()); + + int count =0; + String realString = resultString.substring(1, 207); + + int[] intArr ={17,13,11,10,6,11,6,5,9,1, + 2,2,5,6,5,4,6,5,8,6, + 6,6,2,5,6,4,4,6,6,6, + 1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1}; + String[] strArr = {"vin","startTime","longitude","latitude","speed","mileage","voltage","current", + "resistance","gear","accelerationPedal","brakePedal", + "fuelConsumptionRate","motorControllerTemperature","motorSpeed","motorTorque", + "motorTemperature","motorVoltage","motorCurrent","remainingBattery","maximumFeedbackPower","maximumDischargePower", + "selfCheckCounter","totalBatteryCurrent","totalBatteryVoltage","singleBatteryMaxVoltage","singleBatteryMinVoltage", + "singleBatteryMaxTemperature","singleBatteryMinTemperature","availableBatteryCapacity","vehicleStatus", + "chargingStatus","operatingStatus","socStatus","chargingEnergyStorageStatus","driveMotorStatus", + "positionStatus","easStatus","ptcStatus","epsStatus","absStatus","mcuStatus", + "heatingStatus","batteryStatus","batteryInsulationStatus","dcdcStatus","chgStatus"}; + LinkedHashMap linkedHashMap = new LinkedHashMap<>(); + for (int i = 0; i < 47; i++) { + String substring = realString.substring(count, count + intArr[i]); + linkedHashMap.put(strArr[i],substring); + count = count + intArr[i]; + } + log.warn("hashMap:{}",linkedHashMap); + ObjectMapper objectMapper = new ObjectMapper(); + try { + String json = objectMapper.writeValueAsString(linkedHashMap); + log.error("json格式:{}",json); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } } @@ -98,4 +142,5 @@ public class SummarizeController { return true; } + } diff --git a/src/main/java/com/hyc/kafka/demo/config/KafkaConsumerConfig.java b/src/main/java/com/hyc/kafka/demo/config/KafkaConsumerConfig.java new file mode 100644 index 0000000..b39ee0d --- /dev/null +++ b/src/main/java/com/hyc/kafka/demo/config/KafkaConsumerConfig.java @@ -0,0 +1,112 @@ +package com.hyc.kafka.demo.config; + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.config.KafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.listener.ContainerProperties; +import org.springframework.kafka.support.serializer.JsonDeserializer; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author 李帆 + * @date 2022/10/31 18:05 + * kafka配置,也可以写在yml,这个文件会覆盖yml + */ +@Configuration +public class KafkaConsumerConfig { + + @Value("${spring.kafka.consumer.bootstrap-servers}") + private String bootstrapServers; + @Value("${spring.kafka.consumer.group-id}") + private String groupId; + @Value("${spring.kafka.consumer.enable-auto-commit}") + private boolean enableAutoCommit; + @Value("${spring.kafka.properties.session.timeout.ms}") + private String sessionTimeout; + @Value("${spring.kafka.properties.max.poll.interval.ms}") + private String maxPollIntervalTime; + @Value("${spring.kafka.consumer.max-poll-records}") + private String maxPollRecords; + @Value("${spring.kafka.consumer.auto-offset-reset}") + private String autoOffsetReset; + @Value("${spring.kafka.listener.concurrency}") + private Integer concurrency; + @Value("${spring.kafka.listener.missing-topics-fatal}") + private boolean missingTopicsFatal; + @Value("${spring.kafka.listener.poll-timeout}") + private long pollTimeout; + + @Bean + public Map consumerConfigs() { + + Map propsMap = new HashMap<>(16); + propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); + //是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量 + propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit); + //自动提交的时间间隔,自动提交开启时生效 + propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "2000"); + //该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理: + //earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费分区的记录 + //latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据(在消费者启动之后生成的记录) + //none:当各分区都存在已提交的offset时,从提交的offset开始消费;只要有一个分区不存在已提交的offset,则抛出异常 + propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset); + //两次poll之间的最大间隔,默认值为5分钟。如果超过这个间隔会触发reBalance + propsMap.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalTime); + //这个参数定义了poll方法最多可以拉取多少条消息,默认值为500。如果在拉取消息的时候新消息不足500条,那有多少返回多少;如果超过500条,每次只返回500。 + //这个默认值在有些场景下太大,有些场景很难保证能够在5min内处理完500条消息, + //如果消费者无法在5分钟内处理完500条消息的话就会触发reBalance, + //然后这批消息会被分配到另一个消费者中,还是会处理不完,这样这批消息就永远也处理不完。 + //要避免出现上述问题,提前评估好处理一条消息最长需要多少时间,然后覆盖默认的max.poll.records参数 + //注:需要开启BatchListener批量监听才会生效,如果不开启BatchListener则不会出现reBalance情况 + propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords); + //当broker多久没有收到consumer的心跳请求后就触发reBalance,默认值是10s + propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimeout); + //序列化(建议使用Json,这种序列化方式可以无需额外配置传输实体类) + propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); + propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); + return propsMap; + } + + @Bean + public ConsumerFactory consumerFactory() { + // 配置消费者的 Json 反序列化的可信赖包,反序列化实体类需要 + try (JsonDeserializer deserializer = new JsonDeserializer<>()) { + deserializer.trustedPackages("*"); + return new DefaultKafkaConsumerFactory<>(consumerConfigs(), new JsonDeserializer<>(), deserializer); + } + } + + /** + * KafkaListenerContainerFactory是Spring Kafka提供的用于创建KafkaListenerContainer的工厂类。 + * KafkaListenerContainer是一个用于消费Kafka消息的容器,它封装了Kafka的消费者API,提供了更加方便的使用方式。 + * KafkaListenerContainerFactory可以配置KafkaListenerContainer的一些属性,如消费者的个数、批量消费的大小、消费者的超时时间等。 + * 在Spring Kafka中,可以通过配置KafkaListenerContainerFactory来创建KafkaListenerContainer,从而实现对Kafka消息的消费 + * @return + */ + @Bean + public KafkaListenerContainerFactory> kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + //在侦听器容器中运行的线程数,一般设置为 机器数*分区数 + factory.setConcurrency(concurrency); + // 消费监听接口监听的主题不存在时,默认会报错,所以设置为false忽略错误 + factory.setMissingTopicsFatal(missingTopicsFatal); + // 自动提交关闭,需要设置手动消息确认 + factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); + factory.getContainerProperties().setPollTimeout(pollTimeout); + // 设置为批量监听,需要用List接收 + // factory.setBatchListener(true); + return factory; + } + +} diff --git a/src/main/java/com/hyc/kafka/demo/config/KafkaProviderConfig.java b/src/main/java/com/hyc/kafka/demo/config/KafkaProviderConfig.java new file mode 100644 index 0000000..019b4a6 --- /dev/null +++ b/src/main/java/com/hyc/kafka/demo/config/KafkaProviderConfig.java @@ -0,0 +1,90 @@ +package com.hyc.kafka.demo.config; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.transaction.KafkaTransactionManager; +import org.springframework.kafka.support.serializer.JsonSerializer; + +import java.util.HashMap; +import java.util.Map; + +/** + * kafka 消息的提供者 配置类 + */ +@Configuration +public class KafkaProviderConfig { + + @Value("${spring.kafka.producer.bootstrap-servers}") + private String bootstrapServers; + @Value("${spring.kafka.producer.transaction-id-prefix}") + private String transactionIdPrefix; + @Value("${spring.kafka.producer.acks}") + private String acks; + @Value("${spring.kafka.producer.retries}") + private String retries; + @Value("${spring.kafka.producer.batch-size}") + private String batchSize; + @Value("${spring.kafka.producer.buffer-memory}") + private String bufferMemory; + + /** + * 构建了 Map 存放了 Kafka 生产者的 配置信息 + * @return + */ + @Bean + public Map producerConfigs() { + Map props = new HashMap<>(16); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + //acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。 + //acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。 + //acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。 + //开启事务必须设为all + props.put(ProducerConfig.ACKS_CONFIG, acks); + //发生错误后,消息重发的次数,开启事务必须大于0 + props.put(ProducerConfig.RETRIES_CONFIG, retries); + //当多个消息发送到相同分区时,生产者会将消息打包到一起,以减少请求交互. 而不是一条条发送 + //批次的大小可以通过batch.size 参数设置.默认是16KB + //较小的批次大小有可能降低吞吐量(批次大小为0则完全禁用批处理)。 + //比如说,kafka里的消息5秒钟Batch才凑满了16KB,才能发送出去。那这些消息的延迟就是5秒钟 + //实测batchSize这个参数没有用 + props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); + //有的时刻消息比较少,过了很久,比如5min也没有凑够16KB,这样延时就很大,所以需要一个参数. 再设置一个时间,到了这个时间, + //即使数据没达到16KB,也将这个批次发送出去 + props.put(ProducerConfig.LINGER_MS_CONFIG, "5000"); + //生产者内存缓冲区的大小 + props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory); + //反序列化,和生产者的序列化方式对应 + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + return props; + } + + /** + * 生产者工厂 + * @return + */ + @Bean + public ProducerFactory producerFactory() { + DefaultKafkaProducerFactory factory = new DefaultKafkaProducerFactory<>(producerConfigs()); + //开启事务,会导致 LINGER_MS_CONFIG 配置失效 + factory.setTransactionIdPrefix(transactionIdPrefix); + return factory; + } + + @Bean + public KafkaTransactionManager kafkaTransactionManager(ProducerFactory producerFactory) { + return new KafkaTransactionManager<>(producerFactory); + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } + +} diff --git a/src/main/java/com/hyc/kafka/demo/config/KafkaSendResultHandler.java b/src/main/java/com/hyc/kafka/demo/config/KafkaSendResultHandler.java new file mode 100644 index 0000000..a7f7af7 --- /dev/null +++ b/src/main/java/com/hyc/kafka/demo/config/KafkaSendResultHandler.java @@ -0,0 +1,25 @@ +package com.hyc.kafka.demo.config; + +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.springframework.kafka.support.ProducerListener; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +/** + * @author 李帆 + * @date 2022/10/31 15:41 + * kafka消息发送回调处理 + */ +@Component +public class KafkaSendResultHandler implements ProducerListener { + @Override + public void onSuccess(ProducerRecord producerRecord, RecordMetadata recordMetadata) { + System.out.println("消息发送成功:" + producerRecord.toString()); + } + + @Override + public void onError(ProducerRecord producerRecord, @Nullable RecordMetadata recordMetadata, Exception exception) { + System.out.println("消息发送失败:" + producerRecord.toString() + exception.getMessage()); + } +} diff --git a/src/main/java/com/hyc/kafka/demo/config/MyKafkaListenerErrorHandler.java b/src/main/java/com/hyc/kafka/demo/config/MyKafkaListenerErrorHandler.java new file mode 100644 index 0000000..c96c5c0 --- /dev/null +++ b/src/main/java/com/hyc/kafka/demo/config/MyKafkaListenerErrorHandler.java @@ -0,0 +1,34 @@ +package com.hyc.kafka.demo.config; + +import org.apache.kafka.clients.consumer.Consumer; +import org.springframework.kafka.listener.KafkaListenerErrorHandler; +import org.springframework.kafka.listener.ListenerExecutionFailedException; +import org.springframework.lang.NonNull; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Component; + +/** + * @author 李帆 + * @date 2022/10/31 15:27 + * 消费者消费消息异常处理 + */ +@Component +public class MyKafkaListenerErrorHandler implements KafkaListenerErrorHandler { + + @Override + @NonNull + public Object handleError(@NonNull Message message, @NonNull ListenerExecutionFailedException exception) { + return new Object(); + } + + @Override + @NonNull + public Object handleError(@NonNull Message message, @NonNull ListenerExecutionFailedException exception, + Consumer consumer) { + System.out.println("消息详情:" + message); + System.out.println("异常信息::" + exception); + System.out.println("消费者详情::" + consumer.groupMetadata()); + System.out.println("监听主题::" + consumer.listTopics()); + return KafkaListenerErrorHandler.super.handleError(message, exception, consumer); + } +} diff --git a/src/main/java/com/hyc/kafka/demo/consumer/KafkaConsumer.java b/src/main/java/com/hyc/kafka/demo/consumer/KafkaConsumer.java new file mode 100644 index 0000000..3a316ae --- /dev/null +++ b/src/main/java/com/hyc/kafka/demo/consumer/KafkaConsumer.java @@ -0,0 +1,31 @@ +package com.hyc.kafka.demo.consumer; + +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Service; + +/** + * kafka消费者 + * + * @author YouChe·He + * @ClassName: KafkaConsumer + * @Description: kafka消费者 + * @CreateTime: 2024/6/6 15:33 + */ +@Slf4j +@Service +public class KafkaConsumer { + @KafkaListener(topics = "topic1", groupId = "firstGroup", containerFactory = "kafkaListenerContainerFactory", + errorHandler = "myKafkaListenerErrorHandler") + public void consume(ConsumerRecord consumerRecord, Acknowledgment acknowledgment) { + try { + Object value = consumerRecord.value(); + log.error("消费者得到的数据:{},所在分区:{}",value,consumerRecord.partition()); + }finally { + acknowledgment.acknowledge(); + } + + } +} diff --git a/src/main/java/com/hyc/producer/TransmitMessageProducer.java b/src/main/java/com/hyc/producer/TransmitMessageProducer.java new file mode 100644 index 0000000..97cf404 --- /dev/null +++ b/src/main/java/com/hyc/producer/TransmitMessageProducer.java @@ -0,0 +1,12 @@ +package com.hyc.producer; + +/** + * 传递报文生产者 + * + * @author YouChe·He + * @ClassName: TransmitMessageProducer + * @Description: 传递报文生产者 + * @CreateTime: 2024/6/5 14:35 + */ +public class TransmitMessageProducer { +} diff --git a/src/main/java/com/hyc/util/CalculateCheckDigit.java b/src/main/java/com/hyc/util/CalculateCheckDigit.java new file mode 100644 index 0000000..2caa794 --- /dev/null +++ b/src/main/java/com/hyc/util/CalculateCheckDigit.java @@ -0,0 +1,44 @@ +package com.hyc.util; + +/** + * 校验位计算 + */ +public class CalculateCheckDigit { + + /** + * 不去空格 + * @param sHex + * @return + */ + public static String makeCheck(String sHex){ + return makeCheckSum(sHex.replace(" ", "")); + } + + /** + * 计算校验位 ,返回十六进制校验位 + * */ + private static String makeCheckSum(String data) { + int dSum = 0; + int length = data.length(); + int index = 0; + // 遍历十六进制,并计算总和 + while (index < length) { + // 截取2位字符 + String s = data.substring(index, index + 2); + // 十六进制转成十进制 , 并计算十进制的总和 + dSum += Integer.parseInt(s, 16); + index = index + 2; + } + + // 用256取余,十六进制最大是FF,FF的十进制是255 + int mod = dSum % 256; + // 余数转成十六进制 + String checkSumHex = Integer.toHexString(mod); + length = checkSumHex.length(); + if (length < 2) { + // 校验位不足两位的,在前面补0 + checkSumHex = "0" + checkSumHex; + } + return checkSumHex; + } +} diff --git a/src/main/java/com/hyc/util/ConversionUtil.java b/src/main/java/com/hyc/util/ConversionUtil.java new file mode 100644 index 0000000..e8ebb5f --- /dev/null +++ b/src/main/java/com/hyc/util/ConversionUtil.java @@ -0,0 +1,65 @@ +package com.hyc.util; + +import java.nio.charset.StandardCharsets; + +public class ConversionUtil { + + /** + * 字符串转化成为16进制字符串 + * @param s + * @return + */ + public static String strToSixteen(String s) { + StringBuilder sb = new StringBuilder(); + int length = s.length(); + for (int i = 0; i < length; i++) { + int ch = s.charAt(i); + String s4 = Integer.toHexString(ch); + sb.append(s4 + " "); + } + return sb.toString(); + } + + public static void main (String[] args) { +// String str = "\n" + +// "Sinus
80No Change0
"; +// String strToSixteen = strToSixteen(str); +// System.out.println(str); +// System.out.println(str.length()); +// System.out.println(strToSixteen); +// System.out.println(strToSixteen.replace(" ", "").length()); + + String hexStr = "3C3F786D6C2076657273696F6E3D22312E30223F3E0D0A3C6D6F6E69746F72526F6F7420747970653D22706172616D223E3C73796E6368726F6E697A65537970746F6D206576656E743D22302220696E697469616C3D2274727565223E3C416374696F6E5F4543473E3C52687974686D3E53696E75733C2F52687974686D3E3C48523E38303C2F48523E3C454D443E4E6F204368616E67653C2F454D443E3C436F6E647563743E303C2F436F6E647563743E3C2F416374696F6E5F4543473E3C416374696F6E5F4F7361742076616C75653D2239342220697352656C617469766550657263656E743D2266616C7365222F3E3C416374696F6E5F425020697352656C617469766550657263656E743D2266616C7365223E3C536872696E6B2076616C75653D22313230222F3E3C537472657463682076616C75653D223830222F3E3C2F416374696F6E5F42503E3C416374696F6E5F5265737020627265617468547970653D224E6F726D616C222076616C75653D2231342220697352656C617469766550657263656E743D2266616C7365222F3E3C416374696F6E5F6574434F322076616C75653D2233342220697352656C617469766550657263656E743D2266616C7365222F3E3C416374696F6E5F54656D70657261747572652076616C75653D2233352E32222F3E3C416374696F6E5F4356502076616C75653D22362E30222F3E3C416374696F6E5F5041504469612076616C75653D223130222F3E3C416374696F6E5F5041505379732076616C75653D223235222F3E3C416374696F6E5F57502076616C75653D2239222F3E3C2F73796E6368726F6E697A65537970746F6D3E3C2F6D6F6E69746F72526F6F743E0D0A"; + String hexStringToString = hexStringToString(hexStr); + System.out.println(hexStr); + System.out.println(hexStr.length()); + System.out.println(hexStringToString); + System.out.println(hexStringToString.length()); + } + + /** + * 16进制转换成为string类型字符串 + * @param s + * @return + */ + public static String hexStringToString(String s) { + if (s == null || s.equals("")) { + return null; + } + s = s.replace(" ", ""); + byte[] baKeyword = new byte[s.length() / 2]; + for (int i = 0; i < baKeyword.length; i++) { + try { + baKeyword[i] = (byte) (0xff & Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16)); + } catch (Exception e) { + e.printStackTrace(); + } + } + try { + s = new String(baKeyword, StandardCharsets.UTF_8); + } catch (Exception e1) { + e1.printStackTrace(); + } + return s; + } +} diff --git a/src/main/java/com/hyc/util/Convert.java b/src/main/java/com/hyc/util/Convert.java new file mode 100644 index 0000000..2dc3494 --- /dev/null +++ b/src/main/java/com/hyc/util/Convert.java @@ -0,0 +1,893 @@ +package com.hyc.util; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; + +/** + * 类型转换器 + * + * @author ruoyi + */ +public class Convert { + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static String toStr (Object value, String defaultValue) { + if (null == value) { + return defaultValue; + } + if (value instanceof String) { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static String toStr (Object value) { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Character toChar (Object value, Character defaultValue) { + if (null == value) { + return defaultValue; + } + if (value instanceof Character) { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Character toChar (Object value) { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Byte toByte (Object value, Byte defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Byte) { + return (Byte) value; + } + if (value instanceof Number) { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Byte.parseByte(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Byte toByte (Object value) { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Short toShort (Object value, Short defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Short) { + return (Short) value; + } + if (value instanceof Number) { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Short.parseShort(valueStr.trim()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Short toShort (Object value) { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Number toNumber (Object value, Number defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Number) { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return NumberFormat.getInstance().parse(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Number toNumber (Object value) { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Integer toInt (Object value, Integer defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Integer.parseInt(valueStr.trim()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Integer toInt (Object value) { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * + * @return 结果 + */ + public static Integer[] toIntArray (String str) { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * + * @return 结果 + */ + public static Long[] toLongArray (String str) { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * + * @return 结果 + */ + public static Integer[] toIntArray (String split, String str) { + if (StringUtils.isEmpty(str)) { + return new Integer[]{}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0 ; i < arr.length ; i++) { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * + * @return 结果 + */ + public static Long[] toLongArray (String split, String str) { + if (StringUtils.isEmpty(str)) { + return new Long[]{}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0 ; i < arr.length ; i++) { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * + * @return 结果 + */ + public static String[] toStrArray (String str) { + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * + * @return 结果 + */ + public static String[] toStrArray (String split, String str) { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Long toLong (Object value, Long defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Long) { + return (Long) value; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Long toLong (Object value) { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Double toDouble (Object value, Double defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Double toDouble (Object value) { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Float toFloat (Object value, Float defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Float) { + return (Float) value; + } + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Float.parseFloat(valueStr.trim()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Float toFloat (Object value) { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static Boolean toBool (Object value, Boolean defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) { + case "true": + case "yes": + case "ok": + case "1": + return true; + case "false": + case "no": + case "0": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static Boolean toBool (Object value) { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * + * @return Enum + */ + public static > E toEnum (Class clazz, Object value, E defaultValue) { + if (value == null) { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Enum.valueOf(clazz, valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * + * @return Enum + */ + public static > E toEnum (Class clazz, Object value) { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static BigInteger toBigInteger (Object value, BigInteger defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof BigInteger) { + return (BigInteger) value; + } + if (value instanceof Long) { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return new BigInteger(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static BigInteger toBigInteger (Object value) { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * + * @return 结果 + */ + public static BigDecimal toBigDecimal (Object value, BigDecimal defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + if (value instanceof Long) { + return new BigDecimal((Long) value); + } + if (value instanceof Double) { + return BigDecimal.valueOf((Double) value); + } + if (value instanceof Integer) { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return new BigDecimal(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * + * @return 结果 + */ + public static BigDecimal toBigDecimal (Object value) { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * + * @return 字符串 + */ + public static String utf8Str (Object obj) { + return str(obj, "utf-8"); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * + * @return 字符串 + */ + public static String str (Object obj, String charsetName) { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * + * @return 字符串 + */ + public static String str (Object obj, Charset charset) { + if (null == obj) { + return null; + } + + if (obj instanceof String) { + return (String) obj; + } else if (obj instanceof byte[]) { + return str((byte[]) obj, charset); + } else if (obj instanceof Byte[]) { + byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj); + return str(bytes, charset); + } else if (obj instanceof ByteBuffer) { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * + * @return 字符串 + */ + public static String str (byte[] bytes, String charset) { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * + * @return 解码后的字符串 + */ + public static String str (byte[] data, Charset charset) { + if (data == null) { + return null; + } + + if (null == charset) { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * + * @return 字符串 + */ + public static String str (ByteBuffer data, String charset) { + if (data == null) { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * + * @return 字符串 + */ + public static String str (ByteBuffer data, Charset charset) { + if (null == charset) { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + + /** + * 半角转全角 + * + * @param input String. + * + * @return 全角字符串. + */ + public static String toSBC (String input) { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * + * @return 全角字符串. + */ + public static String toSBC (String input, Set notConvertSet) { + char[] c = input.toCharArray(); + for (int i = 0 ; i < c.length ; i++) { + if (null != notConvertSet && notConvertSet.contains(c[i])) { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') { + c[i] = '\u3000'; + } else if (c[i] < '\177') { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * + * @return 半角字符串 + */ + public static String toDBC (String input) { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * + * @return 替换后的字符 + */ + public static String toDBC (String text, Set notConvertSet) { + char[] c = text.toCharArray(); + for (int i = 0 ; i < c.length ; i++) { + if (null != notConvertSet && notConvertSet.contains(c[i])) { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') { + c[i] = ' '; + } else if (c[i] > '\uFF00' && c[i] < '\uFF5F') { + c[i] = (char) (c[i] - 65248); + } + } + String returnString = new String(c); + + return returnString; + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * + * @return 中文大写数字 + */ + public static String digitUppercase (double n) { + String[] fraction = {"角", "分"}; + String[] digit = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}; + String[][] unit = {{"元", "万", "亿"}, {"", "拾", "佰", "仟"}}; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0 ; i < fraction.length ; i++) { + s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0 ; i < unit[0].length && integerPart > 0 ; i++) { + String p = ""; + for (int j = 0 ; j < unit[1].length && n > 0 ; j++) { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/src/main/java/com/hyc/util/MD5Util.java b/src/main/java/com/hyc/util/MD5Util.java new file mode 100644 index 0000000..d9c23b5 --- /dev/null +++ b/src/main/java/com/hyc/util/MD5Util.java @@ -0,0 +1,70 @@ +package com.hyc.util; + +import lombok.extern.log4j.Log4j2; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; + +@Log4j2 +public class MD5Util { + private static final Integer SALT_LENGTH = 12; + + /** + * 将指定byte数组转换成16进制字符串 + * @param b 字节数组 + * @return 返回结果字符串 + */ + public static String byteToHexString(byte[] b) { + StringBuilder hexString = new StringBuilder(); + for (byte value : b) { + String hex = Integer.toHexString(value & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + } + hexString.append(hex.toUpperCase()); + } + return hexString.toString(); + } + + /** + * 获得加密后的口令 + * @param str 需要加密的字符串 + * @return 加密后的字符串 + */ + public static String encrypted (String str) { + try { + // 声明加密后的口令数组变量 + byte[] pwd = null; + // 随机数生成器 + SecureRandom random = new SecureRandom(); + // 声明盐数组变量 + byte[] salt = new byte[SALT_LENGTH]; + // 将随机数放入盐变量中 + random.nextBytes(salt); + + // 声明消息摘要对象 + MessageDigest md = null; + // 创建消息摘要 + md = MessageDigest.getInstance("MD5"); + // 将盐数据传入消息摘要对象 + md.update(salt); + // 将口令的数据传给消息摘要对象 + md.update(str.getBytes(StandardCharsets.UTF_8)); + // 获得消息摘要的字节数组 + byte[] digest = md.digest(); + + // 因为要在口令的字节数组中存放盐,所以加上盐的字节长度 + pwd = new byte[digest.length + SALT_LENGTH]; + // 将盐的字节拷贝到生成的加密口令字节数组的前12个字节,以便在验证口令时取出盐 + System.arraycopy(salt, 0, pwd, 0, SALT_LENGTH); + // 将消息摘要拷贝到加密口令字节数组从第13个字节开始的字节 + System.arraycopy(digest, 0, pwd, SALT_LENGTH, digest.length); + // 将字节数组格式加密后的口令转化为16进制字符串格式的口令 + return byteToHexString(pwd); + }catch (Exception exception){ + log.info("md5加密失败:[{}]", str, exception); + return str; + } + } +} diff --git a/src/main/java/com/hyc/util/ReflectUtils.java b/src/main/java/com/hyc/util/ReflectUtils.java new file mode 100644 index 0000000..32d2ea8 --- /dev/null +++ b/src/main/java/com/hyc/util/ReflectUtils.java @@ -0,0 +1,314 @@ +package com.hyc.util; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.*; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author ruoyi + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils { + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter (Object obj, String propertyName) { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter (Object obj, String propertyName, E value) { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0 ; i < names.length ; i++) { + if (i < names.length - 1) { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{}); + } else { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[]{value}); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue (final Object obj, final String fieldName) { + Field field = getAccessibleField(obj, fieldName); + if (field == null) { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try { + result = (E) field.get(obj); + } catch (IllegalAccessException e) { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue (final Object obj, final String fieldName, final E value) { + Field field = getAccessibleField(obj, fieldName); + if (field == null) { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try { + field.set(obj, value); + } catch (IllegalAccessException e) { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod (final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) { + if (obj == null || methodName == null) { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try { + return (E) method.invoke(obj, args); + } catch (Exception e) { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName (final Object obj, final String methodName, final Object[] args) { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0 ; i < cs.length ; i++) { + if (args[i] != null && !args[i].getClass().equals(cs[i])) { + if (cs[i] == String.class) { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } else if (cs[i] == Integer.class) { + args[i] = Convert.toInt(args[i]); + } else if (cs[i] == Long.class) { + args[i] = Convert.toLong(args[i]); + } else if (cs[i] == Double.class) { + args[i] = Convert.toDouble(args[i]); + } else if (cs[i] == Float.class) { + args[i] = Convert.toFloat(args[i]); + } else if (cs[i] == boolean.class || cs[i] == Boolean.class) { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } catch (Exception e) { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField (final Object obj, final String fieldName) { + // 为空不报错。直接返回 null + if (obj == null) { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass() ; superClass != Object.class ; superClass = superClass.getSuperclass()) { + try { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } catch (NoSuchFieldException e) { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod (final Object obj, final String methodName, + final Class... parameterTypes) { + // 为空不报错。直接返回 null + if (obj == null) { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass() ; searchType != Object.class ; searchType = searchType.getSuperclass()) { + try { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } catch (NoSuchMethodException e) { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName (final Object obj, final String methodName, int argsNum) { + // 为空不报错。直接返回 null + if (obj == null) { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass() ; searchType != Object.class ; searchType = searchType.getSuperclass()) { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible (Method method) { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible (Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType (final Class clazz) { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType (final Class clazz, final int index) { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass (Object instance) { + if (instance == null) { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked (String msg, Exception e) { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) { + return new IllegalArgumentException(msg, e); + } else if (e instanceof InvocationTargetException) { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6e533f2..e43241f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -48,7 +48,78 @@ spring: host: 47.103.75.98 port: 6379 password: hyc123 + kafka: + producer: + # Kafka服务器 + bootstrap-servers: 115.159.211.196:9092 + # 开启事务,必须在开启了事务的方法中发送,否则报错 + transaction-id-prefix: kafkaTx- + # 发生错误后,消息重发的次数,开启事务必须设置大于0。 + retries: 3 + # acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。 + # acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。 + # acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。 + # 开启事务时,必须设置为all + acks: all + # 当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。 + batch-size: 16384 + # 生产者内存缓冲区的大小。 + buffer-memory: 1024000 + # 键的序列化方式 + key-serializer: org.springframework.kafka.support.serializer.JsonSerializer + # 值的序列化方式(建议使用Json,这种序列化方式可以无需额外配置传输实体类) + value-serializer: org.springframework.kafka.support.serializer.JsonSerializer + consumer: + # Kafka服务器 + bootstrap-servers: 115.159.211.196:9092 + group-id: firstGroup + # 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D + #auto-commit-interval: 2s + # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理: + # earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费分区的记录 + # latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据(在消费者启动之后生成的记录) + # none:当各分区都存在已提交的offset时,从提交的offset开始消费;只要有一个分区不存在已提交的offset,则抛出异常 + auto-offset-reset: latest + # 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量 + enable-auto-commit: false + # 键的反序列化方式 + #key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + key-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer + # 值的反序列化方式(建议使用Json,这种序列化方式可以无需额外配置传输实体类) + value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer + # 配置消费者的 Json 反序列化的可信赖包,反序列化实体类需要 + properties: + spring: + json: + trusted: + packages: "*" + # 这个参数定义了poll方法最多可以拉取多少条消息,默认值为500。如果在拉取消息的时候新消息不足500条,那有多少返回多少;如果超过500条,每次只返回500。 + # 这个默认值在有些场景下太大,有些场景很难保证能够在5min内处理完500条消息, + # 如果消费者无法在5分钟内处理完500条消息的话就会触发reBalance, + # 然后这批消息会被分配到另一个消费者中,还是会处理不完,这样这批消息就永远也处理不完。 + # 要避免出现上述问题,提前评估好处理一条消息最长需要多少时间,然后覆盖默认的max.poll.records参数 + # 注:需要开启BatchListener批量监听才会生效,如果不开启BatchListener则不会出现reBalance情况 + max-poll-records: 3 + properties: + # 两次poll之间的最大间隔,默认值为5分钟。如果超过这个间隔会触发reBalance + max: + poll: + interval: + ms: 600000 + # 当broker多久没有收到consumer的心跳请求后就触发reBalance,默认值是10s + session: + timeout: + ms: 10000 + listener: + # 在侦听器容器中运行的线程数,一般设置为 机器数*分区数 + concurrency: 1 + # 自动提交关闭,需要设置手动消息确认 + ack-mode: manual_immediate + # 消费监听接口监听的主题不存在时,默认会报错,所以设置为false忽略错误 + missing-topics-fatal: false + # 两次poll之间的最大间隔,默认值为5分钟。如果超过这个间隔会触发reBalance + poll-timeout: 600000 # mybatis mybatis: