minio集成

This commit is contained in:
attiya 2025-10-14 11:09:22 +08:00
parent 2c6ccdf0fc
commit fed8f1ed54
10 changed files with 580 additions and 21 deletions

View File

@ -12,21 +12,10 @@
<version>0.0.1-SNAPSHOT</version>
<name>ebike-operations</name>
<description>ebike-operations</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
<docker.image.prefix>ebike_plus</docker.image.prefix>
</properties>
<dependencies>
<dependency>
@ -177,6 +166,11 @@
<artifactId>core</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
</dependencies>
<profiles>

View File

@ -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);
}
}
}

View File

@ -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 {
}

View File

@ -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<String> getAllBucket() throws Exception{
List<Bucket> 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;
}
}

View File

@ -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);
//}
/**
* 生成二维码
* <p>增加内容文字
*
* @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);
}
}
}

View File

@ -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<NetworkInterface> 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;
}
}
}

View File

@ -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

View File

@ -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<String> 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);
}
}

View File

@ -15,7 +15,7 @@
<properties>
<java.version>17</java.version>
<docker.image.prefix>cdzy_ebike</docker.image.prefix>
<docker.image.prefix>ebike_plus</docker.image.prefix>
</properties>
<dependencies>

View File

@ -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