增加内容文字
+ *
+ * @param content 二维码内容
+ * @param text 文字内容
+ * @return Base64编码的二维码
+ */
+ public static BufferedImage generateQRCode(String content, String text) {
+ // 1. 生成基础二维码
+ try (ByteArrayOutputStream qrStream = QRCode.from(content)
+ .withSize(300, 300)
+ .withCharset("UTF-8")
+ .withErrorCorrection(ErrorCorrectionLevel.H) // 纠错等级(H为最高)
+ .to(ImageType.PNG) // 输出格式
+ .stream()) {
+ if(text == null || text.isEmpty()){
+ throw new RuntimeException("二维码内容为空");
+ }
+
+ // 2. 将二维码转换为 BufferedImage
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(qrStream.toByteArray());
+ BufferedImage qrImage = ImageIO.read(inputStream);
+
+ // 3. 创建新 BufferedImage 并绘制文字
+ BufferedImage finalImage = new BufferedImage(
+ qrImage.getWidth(), qrImage.getHeight()+ 20, // 增加文字区域高度,
+ BufferedImage.TYPE_INT_ARGB
+ );
+ Graphics2D g2d = finalImage.createGraphics();
+ g2d.drawImage(qrImage, 0, 0, null);
+
+ // 设置文字样式
+ g2d.setFont(new Font("Arial", Font.BOLD, 20));
+ g2d.setColor(Color.BLACK);
+
+ // 计算文字位置(底部居中)
+ int textWidth = g2d.getFontMetrics().stringWidth(text);
+ int x = (qrImage.getWidth() - textWidth) / 2;
+ int y = qrImage.getHeight() - 2; // 底部留白
+
+ // 添加文字说明
+ g2d.drawString(text, x, y);
+ g2d.dispose();
+
+ // 使用 Java 8+ 的 Base64 编码(避免自动换行问题)
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ ImageIO.write(finalImage, "PNG", stream);
+ return finalImage;
+ } catch (Exception e) {
+ throw new RuntimeException("生成二维码失败", e);
+ }
+ }
+
+
+ public static ByteArrayInputStream generateQRCodeInputStearm(String content, String text) {
+ // 1. 生成基础二维码
+ try (ByteArrayOutputStream qrStream = QRCode.from(content)
+ .withSize(300, 300)
+ .withCharset("UTF-8")
+ .withErrorCorrection(ErrorCorrectionLevel.H) // 纠错等级(H为最高)
+ .to(ImageType.PNG) // 输出格式
+ .stream()) {
+ if(text == null || text.isEmpty()){
+ throw new RuntimeException("二维码内容为空");
+ }
+
+ // 2. 将二维码转换为 BufferedImage
+
+ return new ByteArrayInputStream(qrStream.toByteArray());
+ } catch (Exception e) {
+ throw new RuntimeException("生成二维码失败", e);
+ }
+ }
}
diff --git a/ebike-operations/src/main/java/com/cdzy/operations/utils/VehicleNumberUtil.java b/ebike-operations/src/main/java/com/cdzy/operations/utils/VehicleNumberUtil.java
new file mode 100644
index 0000000..945e25c
--- /dev/null
+++ b/ebike-operations/src/main/java/com/cdzy/operations/utils/VehicleNumberUtil.java
@@ -0,0 +1,198 @@
+package com.cdzy.operations.utils;
+
+import java.net.NetworkInterface;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 车辆编号生成工具类 - 静态方法版本
+ * 基于Snowflake算法改进,保证微服务环境下不重复
+ */
+public final class VehicleNumberUtil {
+
+ // 私有构造方法,防止实例化
+ private VehicleNumberUtil() {
+ throw new AssertionError("不能实例化工具类");
+ }
+
+ // 起始时间戳(2024-01-01)
+ private static final long START_TIMESTAMP = 1704067200000L;
+
+ // 机器ID位数、序列号位数
+ private static final long WORKER_ID_BITS = 5L;
+ private static final long SEQUENCE_BITS = 12L;
+
+ // 最大机器ID、序列号
+ private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
+ private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
+
+ // 移位偏移量
+ private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
+ private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
+
+ // 实例变量转为静态变量,使用Atomic保证线程安全
+ private static final long WORKER_ID;
+ private static final AtomicLastTimestamp LAST_TIMESTAMP = new AtomicLastTimestamp(-1L);
+ private static final AtomicLong SEQUENCE = new AtomicLong(0L);
+
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ // 静态初始化块
+ static {
+ WORKER_ID = generateWorkerId();
+ }
+
+ /**
+ * 生成8位车辆编号(静态方法)
+ * @return 8位数字的车辆编号字符串
+ */
+ public static String generateVehicleNumber() {
+ long timestamp = System.currentTimeMillis();
+ long sequence;
+
+ synchronized (VehicleNumberUtil.class) {
+ long lastTimestamp = LAST_TIMESTAMP.get();
+
+ if (timestamp < lastTimestamp) {
+ throw new RuntimeException("时钟回拨异常,拒绝生成ID");
+ }
+
+ if (timestamp == lastTimestamp) {
+ sequence = SEQUENCE.incrementAndGet() & MAX_SEQUENCE;
+ if (sequence == 0) {
+ timestamp = waitNextMillis(lastTimestamp);
+ }
+ } else {
+ // 新的时间戳,重置序列号为随机值
+ SEQUENCE.set(RANDOM.nextInt((int) MAX_SEQUENCE / 2));
+ sequence = SEQUENCE.get();
+ }
+
+ LAST_TIMESTAMP.set(timestamp);
+ }
+
+ // 生成ID
+ long id = ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
+ | (WORKER_ID << WORKER_ID_SHIFT)
+ | sequence;
+
+ return formatTo8Digits(id);
+ }
+
+ /**
+ * 批量生成车辆编号
+ * @param count 生成数量
+ * @return 车辆编号数组
+ */
+ public static String[] generateVehicleNumbers(int count) {
+ if (count <= 0) {
+ throw new IllegalArgumentException("生成数量必须大于0");
+ }
+ if (count > 1000) {
+ throw new IllegalArgumentException("单次生成数量不能超过1000");
+ }
+
+ String[] numbers = new String[count];
+ for (int i = 0; i < count; i++) {
+ numbers[i] = generateVehicleNumber();
+ }
+ return numbers;
+ }
+
+ /**
+ * 格式化为8位数字
+ */
+ private static String formatTo8Digits(long id) {
+ // 取绝对值,避免负数
+ long positiveId = Math.abs(id);
+
+ // 取后8位,确保是8位数
+ String numberStr = String.valueOf(positiveId % 100000000L);
+
+ // 不足8位前面补随机数
+ if (numberStr.length() < 8) {
+ StringBuilder sb = new StringBuilder(numberStr);
+ while (sb.length() < 8) {
+ sb.insert(0, RANDOM.nextInt(10));
+ }
+ return sb.toString();
+ }
+
+ return numberStr;
+ }
+
+ /**
+ * 等待下一毫秒
+ */
+ private static long waitNextMillis(long lastTimestamp) {
+ long timestamp = System.currentTimeMillis();
+ while (timestamp <= lastTimestamp) {
+ timestamp = System.currentTimeMillis();
+ }
+ return timestamp;
+ }
+
+ /**
+ * 生成机器ID - 基于MAC地址和PID
+ */
+ private static long generateWorkerId() {
+ try {
+ long workerId = 0L;
+
+ // 基于MAC地址生成
+ Enumeration