diff --git a/ebike-staff/pom.xml b/ebike-staff/pom.xml
index c4c1257..eb7dcea 100644
--- a/ebike-staff/pom.xml
+++ b/ebike-staff/pom.xml
@@ -156,6 +156,11 @@
spring-boot-starter-actuator
+
+ io.minio
+ minio
+ ${minio.version}
+
diff --git a/ebike-staff/src/main/java/com/cdzy/staff/config/MinioConfig.java b/ebike-staff/src/main/java/com/cdzy/staff/config/MinioConfig.java
new file mode 100644
index 0000000..670f439
--- /dev/null
+++ b/ebike-staff/src/main/java/com/cdzy/staff/config/MinioConfig.java
@@ -0,0 +1,81 @@
+package com.cdzy.staff.config;
+
+import com.cdzy.staff.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-staff/src/main/java/com/cdzy/staff/controller/EbikeImgController.java b/ebike-staff/src/main/java/com/cdzy/staff/controller/EbikeImgController.java
new file mode 100644
index 0000000..8916ac0
--- /dev/null
+++ b/ebike-staff/src/main/java/com/cdzy/staff/controller/EbikeImgController.java
@@ -0,0 +1,103 @@
+package com.cdzy.staff.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.cdzy.common.enums.Message;
+import com.cdzy.common.model.request.PageParam;
+import com.cdzy.common.model.response.JsonResult;
+import com.cdzy.staff.model.entity.EbikeImg;
+import com.cdzy.staff.service.EbikeImgService;
+import com.mybatisflex.core.paginate.Page;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 图片表 控制层。
+ *
+ * @author attiya
+ * @since 2025-12-17
+ */
+@RestController
+@RequestMapping("/ebikeImg")
+public class EbikeImgController {
+
+ @Resource
+ private EbikeImgService ebikeImgService;
+
+ /**
+ * 添加图片。
+ *
+ * @param ebikeImg 图片表
+ * @return {@code true} 添加成功,{@code false} 添加失败
+ */
+ @PostMapping("save")
+ public JsonResult> save(@RequestBody @Validated EbikeImg ebikeImg) {
+ ebikeImg.setCreatedBy(StpUtil.getLoginIdAsLong());
+ ebikeImgService.save(ebikeImg);
+ return JsonResult.success();
+ }
+
+ /**
+ * 根据主键删除图片。
+ *
+ * @param imgId 主键
+ * @return {@code true} 删除成功,{@code false} 删除失败
+ */
+ @GetMapping("remove")
+ public JsonResult> remove(@RequestParam("imgId") Long imgId) {
+ ebikeImgService.removeById(imgId);
+ return JsonResult.success();
+ }
+
+ /**
+ * 根据主键更新图片。
+ *
+ * @param ebikeImg 图片表
+ * @return {@code true} 更新成功,{@code false} 更新失败
+ */
+ @PostMapping("update")
+ public JsonResult> update(@RequestBody @Validated EbikeImg ebikeImg) {
+ ebikeImgService.updateById(ebikeImg);
+ return JsonResult.success();
+ }
+
+ /**
+ * 查询所有图片。
+ *
+ * @return 所有数据
+ */
+ @GetMapping("list")
+ public JsonResult> list() {
+ List list = ebikeImgService.list();
+ return JsonResult.success(list);
+ }
+
+ /**
+ * 分页查询图片表。
+ *
+ * @param pageParam 分页对象
+ * @return 分页对象
+ */
+ @GetMapping("page")
+ public JsonResult> page(PageParam pageParam) {
+ Page page = ebikeImgService.page(pageParam.getPage());
+ return JsonResult.success(page);
+ }
+
+
+ /**
+ * 上传静态图片
+ *
+ * @param file 文件
+ * @return 操作结果
+ */
+ @PostMapping("upload")
+ public JsonResult> upload(@RequestParam("file") MultipartFile file) throws Exception {
+ String result = ebikeImgService.upload(file);
+ return JsonResult.success(Message.SUCCESS, result);
+ }
+
+}
diff --git a/ebike-staff/src/main/java/com/cdzy/staff/mapper/EbikeImgMapper.java b/ebike-staff/src/main/java/com/cdzy/staff/mapper/EbikeImgMapper.java
new file mode 100644
index 0000000..b61c8ba
--- /dev/null
+++ b/ebike-staff/src/main/java/com/cdzy/staff/mapper/EbikeImgMapper.java
@@ -0,0 +1,14 @@
+package com.cdzy.staff.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.cdzy.staff.model.entity.EbikeImg;
+
+/**
+ * 图片表 映射层。
+ *
+ * @author attiya
+ * @since 2025-12-17
+ */
+public interface EbikeImgMapper extends BaseMapper {
+
+}
diff --git a/ebike-staff/src/main/java/com/cdzy/staff/model/entity/EbikeImg.java b/ebike-staff/src/main/java/com/cdzy/staff/model/entity/EbikeImg.java
new file mode 100644
index 0000000..b1616ad
--- /dev/null
+++ b/ebike-staff/src/main/java/com/cdzy/staff/model/entity/EbikeImg.java
@@ -0,0 +1,58 @@
+package com.cdzy.staff.model.entity;
+
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 图片表 实体类。
+ *
+ * @author attiya
+ * @since 2025-12-17
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Table("ebike_img")
+public class EbikeImg implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ private Long imgId;
+
+ /**
+ * 名称
+ */
+ @NotNull(message = "图片名称不能为空")
+ private String imgName;
+
+ /**
+ * 唯一分区编码
+ */
+ @NotNull(message = "图片标识不能为空")
+ private String imgCode;
+
+ @Column(onInsertValue = "now()")
+ private LocalDateTime createdAt;
+
+ private Long createdBy;
+
+ /**
+ * 图片路径
+ */
+ @NotNull(message = "图片路径不能为空")
+ private String fileUrl;
+
+}
diff --git a/ebike-staff/src/main/java/com/cdzy/staff/service/EbikeImgService.java b/ebike-staff/src/main/java/com/cdzy/staff/service/EbikeImgService.java
new file mode 100644
index 0000000..322f45a
--- /dev/null
+++ b/ebike-staff/src/main/java/com/cdzy/staff/service/EbikeImgService.java
@@ -0,0 +1,21 @@
+package com.cdzy.staff.service;
+
+import com.cdzy.staff.model.entity.EbikeImg;
+import com.mybatisflex.core.service.IService;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 图片表 服务层。
+ *
+ * @author attiya
+ * @since 2025-12-17
+ */
+public interface EbikeImgService extends IService {
+
+ /**
+ * 上传图片
+ * @param file 文件
+ * @return 路径
+ */
+ String upload(MultipartFile file) throws Exception;
+}
diff --git a/ebike-staff/src/main/java/com/cdzy/staff/service/impl/EbikeImgServiceImpl.java b/ebike-staff/src/main/java/com/cdzy/staff/service/impl/EbikeImgServiceImpl.java
new file mode 100644
index 0000000..99ba1eb
--- /dev/null
+++ b/ebike-staff/src/main/java/com/cdzy/staff/service/impl/EbikeImgServiceImpl.java
@@ -0,0 +1,36 @@
+package com.cdzy.staff.service.impl;
+
+import cn.hutool.core.io.file.FileNameUtil;
+import com.cdzy.staff.mapper.EbikeImgMapper;
+import com.cdzy.staff.model.entity.EbikeImg;
+import com.cdzy.staff.service.EbikeImgService;
+import com.cdzy.staff.utils.MinioUtil;
+import com.mybatisflex.spring.service.impl.ServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.UUID;
+
+/**
+ * 图片表 服务层实现。
+ *
+ * @author attiya
+ * @since 2025-12-17
+ */
+@Service
+public class EbikeImgServiceImpl extends ServiceImpl implements EbikeImgService{
+
+ private static final String imageUrl = "/img-file/";
+
+
+ @Resource
+ MinioUtil minioUtil;
+
+ @Override
+ public String upload(MultipartFile file) throws Exception {
+ String objectName = imageUrl + UUID.randomUUID().toString().replace("-", "") + "." + FileNameUtil.extName(file.getOriginalFilename());
+ minioUtil.uploadFile(file.getInputStream(), objectName);
+ return minioUtil.getShowUrl(objectName);
+ }
+}
diff --git a/ebike-staff/src/main/java/com/cdzy/staff/utils/MinioUtil.java b/ebike-staff/src/main/java/com/cdzy/staff/utils/MinioUtil.java
new file mode 100644
index 0000000..0fe853c
--- /dev/null
+++ b/ebike-staff/src/main/java/com/cdzy/staff/utils/MinioUtil.java
@@ -0,0 +1,280 @@
+package com.cdzy.staff.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.ArrayList;
+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;
+
+
+ @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{
+ //上传文件
+ uploadFile(inputStream, bucketName, objectName);
+ }
+
+ /**
+ * 获取文件分析连接(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 fileStreams 文件流列表
+ * @param bucket 数据桶名称
+ * @param objectNames 文件名列表
+ * @return 是否全部成功
+ * @throws Exception 异常
+ */
+ public boolean batchUploadFiles(List fileStreams, String bucket,
+ List objectNames) throws Exception {
+ if (fileStreams.size() != objectNames.size()) {
+ throw new IllegalArgumentException("文件流和文件名数量不匹配");
+ }
+
+ List successUploads = new ArrayList<>();
+
+ try {
+ // 确保桶存在
+ createBucket(bucket);
+
+ // 逐个上传文件
+ for (int i = 0; i < fileStreams.size(); i++) {
+ String objectName = objectNames.get(i);
+ InputStream inputStream = fileStreams.get(i);
+
+ minioClient.putObject(PutObjectArgs.builder()
+ .bucket(bucket)
+ .object(objectName)
+ .stream(inputStream, -1, Integer.MAX_VALUE)
+ .build());
+
+ // 记录成功上传的文件
+ successUploads.add(objectName);
+ }
+
+ return true;
+
+ } catch (Exception e) {
+ // 回滚:删除已上传的文件
+ rollbackBatchUpload(bucket, successUploads);
+ throw new Exception("批量上传失败,已回滚", e);
+ }
+ }
+
+ /**
+ * 回滚批量上传操作
+ * @param bucket 数据桶名称
+ * @param objectNames 需要删除的文件名列表
+ */
+ private void rollbackBatchUpload(String bucket, List objectNames) {
+ if (objectNames.isEmpty()) {
+ return;
+ }
+
+ for (String objectName : objectNames) {
+ try {
+ minioClient.removeObject(RemoveObjectArgs.builder()
+ .bucket(bucket)
+ .object(objectName)
+ .build());
+ } catch (Exception rollbackException) {
+ // 记录回滚失败,但不影响主流程
+ System.err.println("回滚删除文件失败: " + objectName + ", 错误: " + rollbackException.getMessage());
+ }
+ }
+ }
+
+ /**
+ * 批量上传到默认桶
+ */
+ public boolean batchUploadFiles(List fileStreams,
+ List objectNames) throws Exception {
+ return batchUploadFiles(fileStreams, bucketName, objectNames);
+ }
+
+ /**
+ * 获取MinIO中指定文件的文件流
+ * @param bucket 数据桶名称
+ * @param objectName 文件名称
+ * @return 文件流
+ * @throws Exception 异常
+ */
+ public InputStream getFileStream(String bucket, String objectName) throws Exception {
+ return minioClient.getObject(
+ GetObjectArgs.builder()
+ .bucket(bucket)
+ .object(objectName)
+ .build()
+ );
+ }
+
+ /**
+ * 获取默认桶中文件的文件流
+ * @param objectName 文件名称
+ * @return 文件流
+ * @throws Exception 异常
+ */
+ public InputStream getFileStream(String objectName) throws Exception {
+ return minioClient.getObject(
+ GetObjectArgs.builder()
+ .bucket(bucketName)
+ .object(objectName)
+ .build()
+ );
+ }
+
+ /**
+ * 获取二维码展示路径
+ * @param objectName 文件名称
+ */
+ public String getShowUrl(String objectName){
+ return baseUrl+objectName;
+ }
+
+
+}
\ No newline at end of file
diff --git a/ebike-staff/src/main/resources/application-dev.yml b/ebike-staff/src/main/resources/application-dev.yml
index b21f17f..9040a4e 100644
--- a/ebike-staff/src/main/resources/application-dev.yml
+++ b/ebike-staff/src/main/resources/application-dev.yml
@@ -92,3 +92,11 @@ logging:
dateformat: yyyy-MM-dd HH:mm:ss.SSS # 包含毫秒
+minio:
+ endpoint: http://47.109.141.125:9000 # MinIO服务器地址
+ access-key: ZIF4HF1LXH0RTB9MZ52O # 访问密钥
+ secret-key: YuFoH+VJVzQbNLRqVMo39dM5pWXCEcMvrCrtgwB0 # 私有密钥
+ bucket-name: static-objects
+ show-url: https://www.cdzhuojing.cn/file
+
+
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 48a4d45..c53b27f 100644
--- a/ebike-staff/src/test/java/com/cdzy/staff/EbikeStaffApplicationTests.java
+++ b/ebike-staff/src/test/java/com/cdzy/staff/EbikeStaffApplicationTests.java
@@ -8,18 +8,18 @@ import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = com.cdzy.staff.EbikeStaffApplication.class)
class EbikeStaffApplicationTests {
- private static final String model_path ="D:/ebike_plus/ebike-operations";
- private static final String mapperPath="D:/ebike_plus/ebike-operations/resources/mapper";
- private static final String packageName ="com.cdzy.operations";
+ private static final String model_path ="D:/ebike_plus/ebike-staff";
+ private static final String mapperPath="D:/ebike_plus/ebike-staff/resources/mapper";
+ private static final String packageName ="com.cdzy.staff";
private static final String[] tables= new String[]{
- "ebike_borrow_battery_record","ebike_record_codes"
+ "ebike_img"
};
@Test
void gen_mybatis_code() {
//配置数据源
HikariDataSource dataSource = new HikariDataSource();
- dataSource.setJdbcUrl("jdbc:postgresql://47.109.71.130/ebike_operations??currentSchema=public&stringtype=unspecified");
+ dataSource.setJdbcUrl("jdbc:postgresql://47.109.71.130/ebike_staff??currentSchema=public&stringtype=unspecified");
dataSource.setUsername("root");
dataSource.setPassword("970529");
//生成全库的