From fed8f1ed54f6fa16485efce5336b680a95aedfdb822bb198239be3725f9d6268 Mon Sep 17 00:00:00 2001 From: attiya <2413103649@qq.com> Date: Tue, 14 Oct 2025 11:09:22 +0800 Subject: [PATCH] =?UTF-8?q?minio=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ebike-operations/pom.xml | 20 +- .../cdzy/operations/config/MinioConfig.java | 81 +++++++ .../controller/EbikeBatteryQrController.java | 18 ++ .../com/cdzy/operations/utils/MinioUtil.java | 175 ++++++++++++++++ .../com/cdzy/operations/utils/QRGenUtil.java | 79 ++++++- .../operations/utils/VehicleNumberUtil.java | 198 ++++++++++++++++++ .../src/main/resources/application-dev.yml | 7 + .../EbikeOperationsApplicationTests.java | 19 +- ebike-staff/pom.xml | 2 +- .../staff/EbikeStaffApplicationTests.java | 2 +- 10 files changed, 580 insertions(+), 21 deletions(-) create mode 100644 ebike-operations/src/main/java/com/cdzy/operations/config/MinioConfig.java create mode 100644 ebike-operations/src/main/java/com/cdzy/operations/controller/EbikeBatteryQrController.java create mode 100644 ebike-operations/src/main/java/com/cdzy/operations/utils/MinioUtil.java create mode 100644 ebike-operations/src/main/java/com/cdzy/operations/utils/VehicleNumberUtil.java diff --git a/ebike-operations/pom.xml b/ebike-operations/pom.xml index 52cb168..6ed2406 100644 --- a/ebike-operations/pom.xml +++ b/ebike-operations/pom.xml @@ -12,21 +12,10 @@ 0.0.1-SNAPSHOT ebike-operations ebike-operations - - - - - - - - - - - - - + 17 + ebike_plus @@ -177,6 +166,11 @@ core 3.5.1 + + io.minio + minio + ${minio.version} + diff --git a/ebike-operations/src/main/java/com/cdzy/operations/config/MinioConfig.java b/ebike-operations/src/main/java/com/cdzy/operations/config/MinioConfig.java new file mode 100644 index 0000000..c835a1a --- /dev/null +++ b/ebike-operations/src/main/java/com/cdzy/operations/config/MinioConfig.java @@ -0,0 +1,81 @@ +package com.cdzy.operations.config; + +import com.cdzy.operations.utils.MinioUtil; +import io.minio.BucketExistsArgs; +import io.minio.MakeBucketArgs; +import io.minio.MinioClient; +import io.minio.SetBucketPolicyArgs; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Setter +@Getter +@Configuration +@ConfigurationProperties(prefix = "minio") +public class MinioConfig { + private String endpoint; + private String accessKey; + private String secretKey; + private String bucketName; + private String showUrl; + + + @Bean + public MinioClient minioClient() throws Exception { + // 1. 创建MinioClient实例 + MinioClient minioClient = MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .build(); + // 2. 检查存储桶是否存在,不存在则创建 + initializeBucket(minioClient, bucketName); + MinioUtil.setBaseUrl(showUrl+"/"+bucketName); + MinioUtil.setBucketName(bucketName); + return minioClient; + } + + private void initializeBucket(MinioClient minioClient, String bucketName) throws Exception { + try { + // 检查存储桶是否存在:cite[1]:cite[5] + boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder() + .bucket(bucketName) + .build()); + + if (!isExist) { + // 创建存储桶:cite[1]:cite[5] + minioClient.makeBucket(MakeBucketArgs.builder() + .bucket(bucketName) + .build()); + // 设置存储桶策略为公共可读 + String policyJson = """ + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": ["s3:GetObject"], + "Resource": ["arn:aws:s3:::%s/*"] + } + ] + } + """.formatted(bucketName); + + minioClient.setBucketPolicy(SetBucketPolicyArgs.builder() + .bucket(bucketName) + .config(policyJson) + .build()); + log.info("存储桶 {} 创建成功", bucketName); + } else { + log.info("存储桶 {} 已存在", bucketName); + } + } catch (Exception e) { + throw new Exception("初始化Minio存储桶失败: " + e.getMessage(), e); + } + } +} diff --git a/ebike-operations/src/main/java/com/cdzy/operations/controller/EbikeBatteryQrController.java b/ebike-operations/src/main/java/com/cdzy/operations/controller/EbikeBatteryQrController.java new file mode 100644 index 0000000..0b520fe --- /dev/null +++ b/ebike-operations/src/main/java/com/cdzy/operations/controller/EbikeBatteryQrController.java @@ -0,0 +1,18 @@ +package com.cdzy.operations.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 电池二维码 控制层。 + * + * @author attiya + * @since 2025-09-15 + */ +@RestController +@RequestMapping("/ebikeBatteryInfo") +public class EbikeBatteryQrController { + + + +} diff --git a/ebike-operations/src/main/java/com/cdzy/operations/utils/MinioUtil.java b/ebike-operations/src/main/java/com/cdzy/operations/utils/MinioUtil.java new file mode 100644 index 0000000..f94043f --- /dev/null +++ b/ebike-operations/src/main/java/com/cdzy/operations/utils/MinioUtil.java @@ -0,0 +1,175 @@ +package com.cdzy.operations.utils; + +import io.minio.*; +import io.minio.http.Method; +import io.minio.messages.Bucket; +import jakarta.annotation.Resource; +import lombok.Getter; +import lombok.Setter; +import org.springframework.stereotype.Component; + +import java.io.InputStream; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Component +public class MinioUtil { + + @Getter + @Setter + private static String baseUrl; + + @Getter + @Setter + private static String bucketName; + + private static final String bikeUrl = "/bike-qr/"; + + + @Resource + private MinioClient minioClient; + + /** + * 创建桶 + * @param bucket 数据桶名称 + * @throws Exception 异常 + */ + public void createBucket(String bucket) throws Exception { + //查看桶是否存在 + boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build()); + if (!exists){ + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build()); + } + } + + /** + * 上传文件 + * @param inputStream 文件流 + * @param bucket 数据桶名称 + * @param objectName 文件名称 + * @throws Exception 异常 + */ + public void uploadFile(InputStream inputStream, String bucket, String objectName) throws Exception{ + //上传文件 + minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName) + .stream(inputStream,-1,Integer.MAX_VALUE).build()); + } + + /** + * 上传文件至默认桶内 + * @param inputStream 文件流 + * @param objectName 文件名称 + * @throws Exception 异常 + */ + public void uploadFile(InputStream inputStream, String objectName) throws Exception{ + //上传文件 + minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName) + .stream(inputStream,-1,Integer.MAX_VALUE).build()); + } + + /** + * 获取文件分析连接(7天有效 + * @param bucket 数据桶名称 + * @param fileName 文件名称 + * @return url + * @throws Exception 异常 + */ + public String getFileUrl(String bucket, String fileName) throws Exception { + + return minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucket) + .object(fileName) + .expiry(7, TimeUnit.DAYS) // 有效期7天 + .build() + ); + } + + /** + * 获取默认桶内文件分析连接(7天有效 + * @param fileName 文件名称 + * @return url + * @throws Exception 异常 + */ + public String getFileUrl(String fileName) throws Exception { + + return minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(fileName) + .expiry(7, TimeUnit.DAYS) // 有效期7天 + .build() + ); + } + + /** + * 获取所有的桶 + * @return 数据桶名称列表 + * @throws Exception 异常 + */ + public List getAllBucket() throws Exception{ + List buckets = minioClient.listBuckets(); + return buckets.stream().map(Bucket::name).collect(Collectors.toList()); + } + + /** + * 下载文件 + * @param bucket 数据桶名称 + * @param objectName 文件名称 + * @return 文件流 + * @throws Exception 异常 + */ + public InputStream downLoad(String bucket,String objectName) throws Exception{ + return minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(objectName).build()); + } + + /** + * 下载默认桶内文件 + * @param objectName 文件名称 + * @return 文件流 + * @throws Exception 异常 + */ + public InputStream downLoad(String objectName) throws Exception{ + return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build()); + } + + /** + * 删除桶 + * @param bucket 数据桶名称 + * @throws Exception 异常 + */ + public void deleteBucket(String bucket) throws Exception{ + minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucket).build()); + } + + /** + * 删除文件 + * @param bucket 数据桶名称 + * @param objectName 文件名称 + * @throws Exception 异常 + */ + public void deleteObject(String bucket,String objectName) throws Exception{ + minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build()); + } + + /** + * 删除默认桶内文件 + * @param objectName 文件名称 + * @throws Exception 异常 + */ + public void deleteObject(String objectName) throws Exception{ + minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()); + } + + /** + * 获取车辆二维码展示路径 + * @param objectName 文件名称 + */ + public String getBikeQrShowUrl(String objectName){ + return baseUrl+bikeUrl+objectName; + } + +} \ No newline at end of file diff --git a/ebike-operations/src/main/java/com/cdzy/operations/utils/QRGenUtil.java b/ebike-operations/src/main/java/com/cdzy/operations/utils/QRGenUtil.java index 8ebce67..ee8a7a6 100644 --- a/ebike-operations/src/main/java/com/cdzy/operations/utils/QRGenUtil.java +++ b/ebike-operations/src/main/java/com/cdzy/operations/utils/QRGenUtil.java @@ -74,10 +74,79 @@ public class QRGenUtil { } } - //public static void main(String[] args) { - // String content = "B72650"; // 二维码内容 - // String base64 = generateQRCodeBase64(content); - // System.out.println("Base64 二维码: " + base64); - //} + /** + * 生成二维码 + *

增加内容文字 + * + * @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 networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + if (networkInterface.isLoopback() || !networkInterface.isUp()) { + continue; + } + byte[] mac = networkInterface.getHardwareAddress(); + if (mac != null) { + for (byte b : mac) { + workerId = ((workerId << 8) | (b & 0xFF)) % (MAX_WORKER_ID + 1); + } + break; // 使用第一个有效的MAC地址 + } + } + + // 结合进程ID增加唯一性 + String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); + if (processName.contains("@")) { + long pid = Long.parseLong(processName.split("@")[0]); + workerId = (workerId + pid) % (MAX_WORKER_ID + 1); + } + + return workerId; + } catch (Exception e) { + // 如果获取失败,使用随机数 + return RANDOM.nextInt((int) MAX_WORKER_ID); + } + } + + /** + * 获取Worker ID(用于调试) + */ + public static long getWorkerId() { + return WORKER_ID; + } + + /** + * 自定义AtomicLastTimestamp类,用于处理时间戳的原子操作 + */ + private static class AtomicLastTimestamp { + private volatile long value; + + public AtomicLastTimestamp(long initialValue) { + this.value = initialValue; + } + + public long get() { + return value; + } + + public void set(long newValue) { + this.value = newValue; + } + } +} \ No newline at end of file diff --git a/ebike-operations/src/main/resources/application-dev.yml b/ebike-operations/src/main/resources/application-dev.yml index 8050021..3d157f3 100644 --- a/ebike-operations/src/main/resources/application-dev.yml +++ b/ebike-operations/src/main/resources/application-dev.yml @@ -87,4 +87,11 @@ management: health: show-details: always +minio: + endpoint: http://47.109.141.125:9000 # MinIO服务器地址 + access-key: ZIF4HF1LXH0RTB9MZ52O # 访问密钥 + secret-key: YuFoH+VJVzQbNLRqVMo39dM5pWXCEcMvrCrtgwB0 # 私有密钥 + bucket-name: operations-objects + show-url: http://47.109.141.125:9000 + diff --git a/ebike-operations/src/test/java/com/cdzy/operations/EbikeOperationsApplicationTests.java b/ebike-operations/src/test/java/com/cdzy/operations/EbikeOperationsApplicationTests.java index 4c73ce1..a541ca5 100644 --- a/ebike-operations/src/test/java/com/cdzy/operations/EbikeOperationsApplicationTests.java +++ b/ebike-operations/src/test/java/com/cdzy/operations/EbikeOperationsApplicationTests.java @@ -1,13 +1,30 @@ package com.cdzy.operations; +import com.cdzy.operations.utils.MinioUtil; +import com.cdzy.operations.utils.QRGenUtil; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.util.List; + @SpringBootTest class EbikeOperationsApplicationTests { + @Autowired + private MinioUtil minioUtil; + @Test - void contextLoads() { + void contextLoads() throws Exception { +// List allBucket = minioUtil.getAllBucket(); +// System.out.println(allBucket.contains("bike-qr")); + ByteArrayInputStream byteArrayInputStream = QRGenUtil.generateQRCodeInputStearm("https://www.cdzhuojing.cn/qrcode?number=" + "060002" + "&_tbScancodeApproach_=scan", "060002"); + minioUtil.uploadFile(byteArrayInputStream,"operations-objects","/bike-qr/test.jpg"); +// minioUtil.deleteObject("bike-qr","test.jpg"); + String showUrl = minioUtil.getBikeQrShowUrl("test.jpg"); + System.out.println(showUrl); } } diff --git a/ebike-staff/pom.xml b/ebike-staff/pom.xml index f6a1dfb..c4c1257 100644 --- a/ebike-staff/pom.xml +++ b/ebike-staff/pom.xml @@ -15,7 +15,7 @@ 17 - cdzy_ebike + ebike_plus diff --git a/ebike-staff/src/test/java/com/cdzy/staff/EbikeStaffApplicationTests.java b/ebike-staff/src/test/java/com/cdzy/staff/EbikeStaffApplicationTests.java index 25b52e4..80ce9cb 100644 --- a/ebike-staff/src/test/java/com/cdzy/staff/EbikeStaffApplicationTests.java +++ b/ebike-staff/src/test/java/com/cdzy/staff/EbikeStaffApplicationTests.java @@ -12,7 +12,7 @@ class EbikeStaffApplicationTests { private static final String mapperPath="D:/ebike_plus/ebike-operations/resources/mapper"; private static final String packageName ="com.cdzy.operations"; private static final String[] tables= new String[]{ - "ebike_agreement" + "ebike_inventory","ebike_inventory_record" }; @Test