doc/add.sql
@@ -81,4 +81,52 @@ ALTER TABLE `product-inventory-management`.`device_maintenance` ADD COLUMN `device_name` varchar(255) NULL AFTER `tenant_id`, ADD COLUMN `device_model` varchar(255) NULL AFTER `device_name`; ADD COLUMN `device_model` varchar(255) NULL AFTER `device_name`; DROP TABLE IF EXISTS storage_attachment; CREATE TABLE storage_attachment ( id BIGINT AUTO_INCREMENT PRIMARY KEY, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, deleted BIGINT DEFAULT 0 NOT NULL, record_type SMALLINT DEFAULT 0 NOT NULL, record_id BIGINT DEFAULT 0 NOT NULL, name VARCHAR(100) DEFAULT '' NOT NULL, storage_blob_id BIGINT DEFAULT 0 NOT NULL ) COMMENT='éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯'; ALTER TABLE storage_attachment COMMENT 'éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯'; ALTER TABLE storage_attachment MODIFY record_type SMALLINT COMMENT 'å ³èçè®°å½ç±»å'; ALTER TABLE storage_attachment MODIFY record_id BIGINT COMMENT 'å ³èçè®°å½id'; ALTER TABLE storage_attachment MODIFY name VARCHAR(100) COMMENT 'åç§°, å¦: file, avatar (åºåå䏿¡è®°å½ä¸åç±»åçéä»¶)'; ALTER TABLE storage_attachment MODIFY storage_blob_id BIGINT COMMENT 'å ³èstorage_blobè®°å½id'; CREATE INDEX idx_storage_attachment_on_record ON storage_attachment (record_type, record_id); CREATE TABLE storage_blob ( id BIGINT AUTO_INCREMENT PRIMARY KEY, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, key_name VARCHAR(150) DEFAULT '' NOT NULL, content_type VARCHAR(100) DEFAULT '' NOT NULL, original_filename VARCHAR(255) DEFAULT '' NOT NULL, bucket_filename VARCHAR(255) DEFAULT '' NOT NULL, bucket_name VARCHAR(255) DEFAULT '' NOT NULL, byte_size BIGINT DEFAULT 0 NOT NULL, UNIQUE (key_name) ) COMMENT='éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯'; ALTER TABLE storage_blob COMMENT 'éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯'; ALTER TABLE storage_blob MODIFY key_name VARCHAR(150) COMMENT 'èµæºid'; ALTER TABLE storage_blob MODIFY content_type VARCHAR(100) COMMENT 'èµæºç±»åï¼ä¾å¦JPGå¾ççèµæºç±»å为image/jpg'; ALTER TABLE storage_blob MODIFY original_filename VARCHAR(255) COMMENT 'åæä»¶åç§°'; ALTER TABLE storage_blob MODIFY bucket_filename VARCHAR(255) COMMENT 'å卿¡¶ä¸æä»¶å'; ALTER TABLE storage_blob MODIFY bucket_name VARCHAR(255) COMMENT 'å卿¡¶å'; ALTER TABLE storage_blob MODIFY byte_size BIGINT COMMENT 'èµæºå°ºå¯¸(åè)'; pom.xml
@@ -37,6 +37,8 @@ <velocity.version>2.3</velocity.version> <!-- override dependency version --> <tomcat.version>9.0.102</tomcat.version> <minio.version>8.4.3</minio.version> <okhttp.version>4.9.0</okhttp.version> <logback.version>1.2.13</logback.version> <spring-security.version>5.7.12</spring-security.version> <spring-framework.version>5.3.39</spring-framework.version> @@ -252,6 +254,28 @@ <artifactId>lombok</artifactId> </dependency> <!-- minio --> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>${minio.version}</version> <exclusions> <exclusion> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> </exclusion> </exclusions> </dependency> <!-- minioä¾èµokhttp ä¸ç¶æ¥é --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>${okhttp.version}</version> </dependency> </dependencies> <build> src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,9 @@ package com.ruoyi.basic.dto; import com.ruoyi.basic.pojo.StorageBlob; import lombok.Data; @Data public class StorageBlobDTO extends StorageBlob { private String url; } src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,18 @@ package com.ruoyi.basic.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.basic.pojo.StorageAttachment; import org.apache.ibatis.annotations.Mapper; /** * <p> * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ Mapper æ¥å£ * </p> * * @author ruoyi * @since 2025-05-29 */ @Mapper public interface StorageAttachmentMapper extends BaseMapper<StorageAttachment> { } src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,18 @@ package com.ruoyi.basic.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.basic.pojo.StorageBlob; import org.apache.ibatis.annotations.Mapper; /** * <p> * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ Mapper æ¥å£ * </p> * * @author ruoyi * @since 2025-05-29 */ @Mapper public interface StorageBlobMapper extends BaseMapper<StorageBlob> { } src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,72 @@ package com.ruoyi.basic.pojo; import com.baomidou.mybatisplus.annotation.*; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.basic.dto.StorageBlobDTO; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ å®ä½ç±» * * @author ruoyi * @date 2025-05-29 */ @Data @TableName("storage_attachment") public class StorageAttachment implements Serializable { private static final long serialVersionUID = 1L; /** * */ @TableId(value = "id", type = IdType.AUTO) private Long id; /** å建æ¶é´ */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(fill = FieldFill.INSERT) private Date createTime; /** æ´æ°æ¶é´ */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; /** * é»è¾å é¤ */ @TableField(value = "deleted") private Long deleted; /** * å ³èçè®°å½ç±»å */ @TableField(value = "record_type") private Long recordType; /** * å ³èçè®°å½id */ @TableField(value = "record_id") private Long recordId; /** * ç±»ååç§°, å¦: file, avatar (åºåå䏿¡è®°å½ä¸åç±»åçéä»¶) */ @TableField(value = "name") private String name; /** * å ³èstorage_blobè®°å½id */ @TableField(value = "storage_blob_id") private Long storageBlobId; private StorageBlobDTO storageBlobDTO; public StorageAttachment(String fileType, Long recordType, Long recordId) { this.name = fileType; this.recordType = recordType; this.recordId = recordId; } } src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,67 @@ package com.ruoyi.basic.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ å®ä½ç±» * * @author ruoyi * @date 2025-05-29 */ @Data @TableName("storage_blob") public class StorageBlob implements Serializable { private static final long serialVersionUID = 1L; /** * */ @TableId(value = "id", type = IdType.AUTO) private Long id; /** å建æ¶é´ */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // @TableField(fill = FieldFill.INSERT) private Date createTime; /** * èµæºid */ @TableField(value = "key") private String key; /** * èµæºç±»åï¼ä¾å¦JPGå¾ççèµæºç±»å为image/jpg */ @TableField(value = "content_type") private String contentType; /** * åæä»¶å */ @TableField(value = "original_filename") private String originalFilename; /** * å卿¡¶ä¸ */ @TableField(value = "bucket_filename") private String bucketFilename; /** * å卿¡¶å */ @TableField(value = "bucket_name") private String bucketName; /** * èµæºå°ºå¯¸(åè) */ @TableField(value = "byte_size") private Long byteSize; } src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,43 @@ package com.ruoyi.basic.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.common.constant.StorageAttachmentConstants; import com.ruoyi.common.enums.StorageAttachmentRecordType; import java.util.List; /** * <p> * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ æå¡ç±» * </p> * * @author ruoyi * @since 2025-05-29 */ public interface StorageAttachmentService extends IService<StorageAttachment> { /** * æ¥è¯¢éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ * @param recordId å ³èè®°å½id * @param recordType å ³èè®°å½ç±»å * @param fileType æä»¶ç±»å * @return æä»¶ä¿¡æ¯å表 */ List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType); /** * ä¿åéç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ * @param attachments æä»¶ä¿¡æ¯å表 * @param recordId 管çè®°å½id * @param recordType å ³èè®°å½ç±»å * @param fileType æä»¶ç±»å */ public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType); /** * å é¤éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ * @param storageAttachment æä»¶ä¿¡æ¯ * @return å é¤ç»æ */ public int deleteStorageAttachment(StorageAttachment storageAttachment); } src/main/java/com/ruoyi/basic/service/StorageBlobService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,35 @@ package com.ruoyi.basic.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.basic.dto.StorageBlobDTO; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.basic.pojo.StorageBlob; import org.springframework.web.multipart.MultipartFile; import java.util.List; /** * <p> * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ æå¡ç±» * </p> * * @author ruoyi * @since 2025-05-29 */ public interface StorageBlobService extends IService<StorageBlob> { /** * æä»¶ä¸ä¼ æ¥å£ * @param files æä»¶ä¿¡æ¯ * @param bucketName å卿¡¶åç§° * @return ä¸ä¼ ç»æ */ List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName); /** * æ¹éå 餿件 * @param attachment * @return */ public int deleteStorageBlobs(StorageAttachment attachment); } src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,91 @@ package com.ruoyi.basic.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.basic.dto.StorageBlobDTO; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.basic.mapper.StorageAttachmentMapper; import com.ruoyi.basic.mapper.StorageBlobMapper; import com.ruoyi.basic.pojo.StorageBlob; import com.ruoyi.basic.service.StorageAttachmentService; import com.ruoyi.basic.service.StorageBlobService; import com.ruoyi.common.constant.StorageAttachmentConstants; import com.ruoyi.common.enums.StorageAttachmentRecordType; import com.ruoyi.common.utils.MinioUtils; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * <p> * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ æå¡å®ç°ç±» * </p> * * @author ruoyi * @since 2025-05-29 */ @Service @RequiredArgsConstructor public class StorageAttachmentServiceImpl extends ServiceImpl<StorageAttachmentMapper, StorageAttachment> implements StorageAttachmentService { @Autowired private StorageBlobMapper storageBlobMapper; @Autowired private StorageAttachmentMapper storageAttachmentMapper; @Autowired private StorageBlobService storageBlobService; @Autowired private MinioUtils minioUtils; @Override public List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) { List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>() .eq(StorageAttachment::getRecordId, recordId) .eq(StorageAttachment::getRecordType, recordType.ordinal()) .eq(StorageAttachment::getName, fileType.toString())); if (storageAttachments != null) { for (StorageAttachment storageAttachment : storageAttachments) { StorageBlob storageBlob = storageBlobMapper.selectById(storageAttachment.getStorageBlobId()); StorageBlobDTO storageBlobDTO = new StorageBlobDTO(); BeanUtils.copyProperties(storageBlob, storageBlobDTO); storageBlobDTO.setUrl(minioUtils.getPreviewUrl(storageBlob.getBucketName(), storageBlob.getBucketName(), true)); storageAttachment.setStorageBlobDTO(storageBlobDTO); } } return storageAttachments; } @Override public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) { // å 餿§å¾ deleteStorageAttachment(new StorageAttachment(fileType.toString(), (long) recordType.ordinal(), recordId)); for (StorageAttachment attachment : attachments) { // è·åå ³èè®°å½ StorageBlob storageBlob = attachment.getStorageBlobDTO(); attachment.setName(fileType.toString()); attachment.setRecordType((long) recordType.ordinal()); attachment.setRecordId(recordId); attachment.setStorageBlobId(storageBlob.getId()); storageAttachmentMapper.insert(attachment); } } @Override public int deleteStorageAttachment(StorageAttachment storageAttachment) { // å å 餿ç»è¡¨ storageBlobService.deleteStorageBlobs(storageAttachment); return storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>() .eq(StorageAttachment::getRecordId, storageAttachment.getRecordId()) .eq(StorageAttachment::getRecordType, storageAttachment.getRecordType()) .eq(StorageAttachment::getName, storageAttachment.getName())); } } src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,103 @@ package com.ruoyi.basic.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.basic.dto.StorageBlobDTO; import com.ruoyi.basic.mapper.StorageAttachmentMapper; import com.ruoyi.basic.mapper.StorageBlobMapper; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.basic.pojo.StorageBlob; import com.ruoyi.basic.service.StorageBlobService; import com.ruoyi.common.exception.file.InvalidExtensionException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.MinioUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.framework.web.domain.MinioResult; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * <p> * éç¨æä»¶ä¸ä¼ çéä»¶ä¿¡æ¯ æå¡å®ç°ç±» * </p> * * @author ruoyi * @since 2025-05-29 */ @Service @RequiredArgsConstructor public class StorageBlobServiceImpl extends ServiceImpl<StorageBlobMapper, StorageBlob> implements StorageBlobService { @Autowired private StorageAttachmentMapper storageAttachmentMapper; @Autowired private StorageBlobMapper storageBlobMapper; @Autowired private MinioUtils minioUtils; @Override public List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName) { // è¥æ²¡ä¼ å ¥bucketNameï¼å使ç¨é»è®¤bucketName if (StringUtils.isEmpty(bucketName)) { bucketName = minioUtils.getDefaultBucket(); } List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>(); for (MultipartFile file : files) { try { MinioResult res = minioUtils.upload(bucketName, file, false); StorageBlobDTO dto = new StorageBlobDTO(); dto.setContentType(file.getContentType()); dto.setBucketFilename(res.getBucketFileName()); dto.setOriginalFilename(res.getOriginalName()); dto.setByteSize(file.getSize()); dto.setKey(IdUtils.simpleUUID()); dto.setBucketName(bucketName); dto.setCreateTime(DateUtils.getNowDate()); dto.setUrl(minioUtils.getPreviewUrl(res.getBucketFileName(), bucketName, false)); // æå ¥æ°æ®åº storageBlobMapper.insert(dto); storageBlobDTOs.add(dto); } catch (InvalidExtensionException e) { throw new RuntimeException("minioæä»¶ä¸ä¼ å¼å¸¸ï¼" + e); } } return storageBlobDTOs; } @Override public int deleteStorageBlobs(StorageAttachment attachment) { List<StorageAttachment> attachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>() .eq(StorageAttachment::getRecordId, attachment.getRecordId()) .eq(StorageAttachment::getRecordType, attachment.getRecordType()) .eq(StorageAttachment::getName, attachment.getName())); List<Long> ids = attachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList()); List<StorageBlob> storageBlobs = storageBlobMapper.selectList(new LambdaQueryWrapper<StorageBlob>() .in(StorageBlob::getId, ids)); if (!storageBlobs.isEmpty()) { for (StorageBlob storageBlob : storageBlobs) { // ç§»é¤æ¡¶å æä»¶ minioUtils.removeObjectsResult(storageBlob.getBucketName(), storageBlob.getBucketName()); } } if (!ids.isEmpty()) { return storageBlobMapper.delete(new QueryWrapper<StorageBlob>().lambda().in(StorageBlob::getId, ids)); } return 0; } } src/main/java/com/ruoyi/common/config/MinioConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,27 @@ package com.ruoyi.common.config; import io.minio.MinioClient; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; @Configuration @Component @ConfigurationProperties(prefix = "minio") @Data public class MinioConfig { private String endpoint; private int port; private String accessKey; private String secretKey; private Boolean secure; @Bean public MinioClient getMinioClient() { return MinioClient.builder().endpoint(endpoint, port, secure) .credentials(accessKey, secretKey) .build(); } } src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,17 @@ package com.ruoyi.common.constant; /** * é件常é */ public class StorageAttachmentConstants { /** * æä»¶ */ public static final String StorageAttachmentFile = "file"; /** * å¾ç */ public static final String StorageAttachmentImage = "image"; } src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,25 @@ package com.ruoyi.common.enums; import lombok.AllArgsConstructor; /** * éä»¶è®°å½ç±»åæä¸¾ * */ @AllArgsConstructor public enum StorageAttachmentRecordType { // ä¾å å®é å¼å请å é¤ Template("Template","èä¾"); private final String code; private final String info; public String getCode() { return code; } public String getInfo() { return info; } } src/main/java/com/ruoyi/common/utils/MinioUtils.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,309 @@ package com.ruoyi.common.utils; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.ruoyi.common.exception.UtilException; import com.ruoyi.common.exception.file.InvalidExtensionException; import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.MimeTypeUtils; import com.ruoyi.framework.web.domain.MinioResult; import io.minio.*; import io.minio.http.Method; import io.minio.messages.DeleteError; import io.minio.messages.DeleteObject; import lombok.Getter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.FastByteArrayOutputStream; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Component public class MinioUtils { @Autowired private MinioClient minioClient; @Value("${minio.preview-expiry}") private Integer previewExpiry; /** * -- GETTER -- * è·åé»è®¤å卿¡¶åç§° * * @return */ @Getter @Value("${minio.default-bucket}") private String defaultBucket; /** * 夿å卿¡¶æ¯å¦åå¨ï¼ä¸åå¨åå建 * * @param bucketName å卿¡¶åç§° */ public void existBucket(String bucketName) { try { boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (!exists) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } } catch (Exception e) { e.printStackTrace(); } } /** * å建å卿¡¶ * * @param bucketName å卿¡¶åç§° * @return æ¯å¦å建æå */ public Boolean makeBucket(String bucketName) { try { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * å é¤å卿¡¶ * * @param bucketName å卿¡¶åç§° * @return æ¯å¦å 餿å */ public Boolean removeBucket(String bucketName) { try { minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * å¤æå¯¹è±¡æ¯å¦åå¨ * * @param bucketName å卿¡¶åç§° * @param originalFileName MinIOä¸åå¨å¯¹è±¡å ¨è·¯å¾ * @return 对象æ¯å¦åå¨ */ public boolean existObject(String bucketName, String originalFileName) { try { minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(originalFileName).build()); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * æä»¶ä¸ä¼ * * @param bucketName å卿¡¶åç§° * @param file æä»¶ * @return æ¡¶ä¸ä½ç½® */ public MinioResult upload(String bucketName, MultipartFile file, Boolean isPreviewExpiry) throws InvalidExtensionException { MultipartFile[] fileArr = {file}; List<MinioResult> fileNames = upload(bucketName, fileArr, isPreviewExpiry); return fileNames.isEmpty() ? null : fileNames.get(0); } /** * ä¸ä¼ æä»¶ * * @param bucketName å卿¡¶åç§° * @param fileList æä»¶å表 * @return æ¡¶ä¸ä½ç½®å表 */ public List<MinioResult> upload(String bucketName, List<MultipartFile> fileList, Boolean isPreviewExpiry) throws InvalidExtensionException { MultipartFile[] fileArr = fileList.toArray(new MultipartFile[0]); return upload(bucketName, fileArr, isPreviewExpiry); } /** * description: ä¸ä¼ æä»¶ * * @param bucketName å卿¡¶åç§° * @param fileArr æä»¶å表 * @return æ¡¶ä¸ä½ç½®å表 */ public List<MinioResult> upload(String bucketName, MultipartFile[] fileArr, Boolean isPreviewExpiry) throws InvalidExtensionException { for (MultipartFile file : fileArr) { FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); } // ä¿è¯æ¡¶ä¸å®åå¨ existBucket(bucketName); // æ§è¡æ£å¸¸æä½ List<MinioResult> bucketFileNames = new ArrayList<>(fileArr.length); for (MultipartFile file : fileArr) { // è·ååå§æä»¶åç§° String originalFileName = file.getOriginalFilename(); // è·åå½åæ¥æï¼æ ¼å¼ä¾å¦ï¼2020-11 String datePath = new SimpleDateFormat("yyyy-MM").format(new Date()); // æä»¶åç§° String uuid = IdWorker.get32UUID(); // è·åæä»¶åç¼ String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); String bucketFilePath = datePath + "/" + uuid + suffix; // æ¨éæä»¶å°MinIO try (InputStream in = file.getInputStream()) { minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(bucketFilePath) .stream(in, in.available(), -1) .contentType(file.getContentType()) .build() ); } catch (Exception e) { throw new UtilException("MinioUtilsï¼ä¸ä¼ æä»¶å·¥å ·ç±»å¼å¸¸:" + e); } MinioResult minioResult = new MinioResult(); minioResult.setBucketFileName(bucketFilePath); // è¿åæ°¸ä¹ é¢è§å°å if (isPreviewExpiry) { String previewUrl = getPreviewUrl(bucketFilePath, bucketName, isPreviewExpiry); minioResult.setPreviewExpiry(previewUrl); } minioResult.setOriginalName(originalFileName); bucketFileNames.add(minioResult); } return bucketFileNames; } /** * æä»¶ä¸è½½ * * @param bucketName å卿¡¶åç§° * @param bucketFileName æ¡¶ä¸æä»¶åç§° * @param originalFileName åå§æä»¶åç§° * @param response response对象 */ public void download(String bucketName, String bucketFileName, String originalFileName, HttpServletResponse response) { GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(bucketFileName).build(); try (GetObjectResponse objResponse = minioClient.getObject(objectArgs)) { byte[] buf = new byte[1024]; int len; try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) { while ((len = objResponse.read(buf)) != -1) { os.write(buf, 0, len); } os.flush(); byte[] bytes = os.toByteArray(); response.setCharacterEncoding("utf-8"); //设置强å¶ä¸è½½ä¸æå¼ response.setContentType("application/force-download"); // 设置éä»¶åç§°ç¼ç originalFileName = new String(originalFileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); // 设置éä»¶åç§° response.addHeader("Content-Disposition", "attachment;fileName=" + originalFileName); // åå ¥æä»¶ try (ServletOutputStream stream = response.getOutputStream()) { stream.write(bytes); stream.flush(); } } } catch (Exception e) { throw new UtilException("MinioUtilsï¼ä¸ä¼ æä»¶å·¥å ·ç±»å¼å¸¸"); } } /** * è·åå·²ä¸ä¼ 对象çæä»¶æµï¼å端å 为ä¸å¡éè¦è·åæä»¶æµå¯ä»¥è°ç¨è¯¥æ¹æ³ï¼ * * @param bucketName å卿¡¶åç§° * @param bucketFileName æ¡¶ä¸æä»¶åç§° * @return æä»¶æµ */ public InputStream getFileStream(String bucketName, String bucketFileName) throws Exception { GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(bucketFileName).build(); return minioClient.getObject(objectArgs); } /** * æ¹éå é¤æä»¶å¯¹è±¡ç»æ * * @param bucketName å卿¡¶åç§° * @param bucketFileName æ¡¶ä¸æä»¶åç§° * @return å é¤ç»æ */ public DeleteError removeObjectsResult(String bucketName, String bucketFileName) { List<DeleteError> results = removeObjectsResult(bucketName, Collections.singletonList(bucketFileName)); return !results.isEmpty() ? results.get(0) : null; } /** * æ¹éå é¤æä»¶å¯¹è±¡ç»æ * * @param bucketName å卿¡¶åç§° * @param bucketFileNames æ¡¶ä¸æä»¶åç§°éå * @return å é¤ç»æ */ public List<DeleteError> removeObjectsResult(String bucketName, List<String> bucketFileNames) { Iterable<Result<DeleteError>> results = removeObjects(bucketName, bucketFileNames); List<DeleteError> res = new ArrayList<>(); for (Result<DeleteError> result : results) { try { res.add(result.get()); } catch (Exception e) { throw new UtilException("MinioUtilsï¼ä¸ä¼ æä»¶å·¥å ·ç±»å¼å¸¸"); } } return res; } /** * æ¹éå é¤æä»¶å¯¹è±¡ * * @param bucketName å卿¡¶åç§° * @param bucketFileNames æ¡¶ä¸æä»¶åç§°éå */ private Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> bucketFileNames) { List<DeleteObject> dos = bucketFileNames.stream().map(DeleteObject::new).collect(Collectors.toList()); return minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build()); } /** * æ¥è¯¢é¢è§url * @param bucketFileName minioæä»¶åç§° * @param bucketName å卿¡¶åç§° * @param isPreviewExpiry æ¯å¦éè¦è¿ææ¶é´ é»è®¤24å°æ¶ * @return */ public String getPreviewUrl(String bucketFileName, String bucketName, Boolean isPreviewExpiry) { if (StringUtils.isNotBlank(bucketFileName)) { try { minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(bucketFileName).build()); // 为falseåªçæ24å°æ¶æææ¶é¿çurl龿¥ï¼å¯ä»¥è®¿é®è¯¥æä»¶ if (isPreviewExpiry){ return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(bucketFileName).build()); }else { return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(bucketFileName).expiry(previewExpiry, TimeUnit.HOURS).build()); } } catch (Exception e) { throw new UtilException("MinioUtilsï¼ä¸ä¼ æä»¶å·¥å ·ç±»å¼å¸¸"); } } return null; } } src/main/java/com/ruoyi/framework/web/domain/MinioResult.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,16 @@ package com.ruoyi.framework.web.domain; import lombok.Data; @Data public class MinioResult { // minioä¸çæä»¶åç§° private String bucketFileName; // æºæä»¶åç§° private String originalName; // é¢è§è·¯å¾ private String previewExpiry; } src/main/java/com/ruoyi/other/controller/TempFileController.java
@@ -3,8 +3,13 @@ import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.other.service.TempFileService; import com.ruoyi.purchase.dto.ProductRecordDto; import com.ruoyi.purchase.dto.TicketRegistrationDto; import com.ruoyi.purchase.service.ITicketRegistrationService; import com.ruoyi.purchase.service.impl.TicketRegistrationServiceImpl; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -17,6 +22,8 @@ private TempFileService tempFileService; private TicketRegistrationServiceImpl ticketRegistrationServiceImpl; @PostMapping("/upload") public AjaxResult uploadFile(MultipartFile file, Integer type) { try { @@ -26,4 +33,16 @@ } } @PostMapping("uploadFile") public AjaxResult uploadFile(@RequestBody ProductRecordDto productRecordDto) { try { if (!productRecordDto.getTempFileIds().isEmpty()&&productRecordDto.getTicketRegistrationId() != null) { ticketRegistrationServiceImpl.migrateTempFilesToFormal(productRecordDto.getTicketRegistrationId(), productRecordDto.getTempFileIds()); } } catch (Exception e) { return AjaxResult.error(e.getMessage()); } return AjaxResult.success(); } } src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java
@@ -19,6 +19,7 @@ import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import org.springframework.security.core.parameters.P; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -82,6 +83,22 @@ return toAjax(ticketRegistrationService.addOrUpdateRegistration(ticketRegistrationDto)); } @GetMapping("/getProductRecordById") public AjaxResult getProductRecordById(Long id) { if (id == null) { return AjaxResult.error("åæ°é误"); } return AjaxResult.success(productRecordService.getProductRecordById(id)); } @ApiModelProperty("ä¿®æ¹æ¥ç¥¨ç»è®°") @PostMapping("/updateRegistration") public AjaxResult updateRegistration(@RequestBody ProductRecordDto productRecordDto) { return productRecordService.updateRecord(productRecordDto); } /** * å 餿¥ç¥¨ç»è®° */ src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java
@@ -1,5 +1,6 @@ package com.ruoyi.purchase.dto; import com.ruoyi.other.pojo.TempFile; import com.ruoyi.purchase.pojo.ProductRecord; import com.ruoyi.sales.pojo.CommonFile; import lombok.Data; @@ -30,4 +31,6 @@ private String unTicketsPrice = "0"; private List<CommonFile> commonFiles; private List<String> tempFileIds; } src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java
@@ -17,4 +17,6 @@ public interface ProductRecordMapper extends BaseMapper<ProductRecord> { IPage<ProductRecordDto> productRecordPage(Page page, @Param("c") TicketRegistrationDto ticketRegistrationDto); ProductRecordDto getProductRecordById(Long id); } src/main/java/com/ruoyi/purchase/service/IProductRecordService.java
@@ -3,6 +3,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.purchase.dto.ProductRecordDto; import com.ruoyi.purchase.dto.TicketRegistrationDto; import com.ruoyi.purchase.pojo.ProductRecord; @@ -20,4 +21,8 @@ List<ProductRecord> selectProductRecordList(TicketRegistrationDto ticketRegistrationDto); IPage<ProductRecordDto> productRecordPage(Page page, TicketRegistrationDto ticketRegistrationDto); AjaxResult updateRecord(ProductRecordDto productRecordDto); ProductRecordDto getProductRecordById(Long id); } src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java
@@ -5,15 +5,21 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.enums.FileNameType; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.purchase.dto.ProductRecordDto; import com.ruoyi.purchase.dto.TicketRegistrationDto; import com.ruoyi.purchase.mapper.ProductRecordMapper; import com.ruoyi.purchase.mapper.PurchaseLedgerMapper; import com.ruoyi.purchase.pojo.ProductRecord; import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.purchase.service.IProductRecordService; import com.ruoyi.sales.mapper.CommonFileMapper; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.sales.pojo.CommonFile; import com.ruoyi.sales.pojo.SalesLedgerProduct; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -34,8 +40,10 @@ @Autowired private CommonFileMapper commonFileMapper; @Autowired private SalesLedgerProductMapper salesLedgerProductMapper; @Autowired private PurchaseLedgerMapper purchaseLedgerMapper; /** @@ -61,4 +69,30 @@ }); return productRecordDtoIPage; } @Override public AjaxResult updateRecord(ProductRecordDto productRecordDto) { SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productRecordDto.getSaleLedgerProjectId()); ProductRecord productRecord = productRecordMapper.selectById(productRecordDto.getId()); if (salesLedgerProduct != null) { salesLedgerProduct.setFutureTicketsAmount(salesLedgerProduct.getFutureTicketsAmount().add(productRecord.getTicketsAmount()).subtract(productRecordDto.getTicketsAmount())); salesLedgerProduct.setFutureTickets(salesLedgerProduct.getFutureTickets().add(productRecord.getTicketsNum().subtract(productRecordDto.getTicketsNum()))); salesLedgerProductMapper.updateById(salesLedgerProduct); } PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(productRecord.getPurchaseLedgerId()); if (purchaseLedger != null) { purchaseLedger.setReceiptPaymentAmount(purchaseLedger.getReceiptPaymentAmount()); } BeanUtils.copyProperties(productRecordDto,productRecord); productRecordMapper.updateById(productRecord); return AjaxResult.success("ä¿®æ¹æå"); } @Override public ProductRecordDto getProductRecordById(Long id) { ProductRecordDto productRecordDto = productRecordMapper.getProductRecordById(id); return productRecordDto; } } src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java
@@ -178,7 +178,7 @@ * @param tempFileIds ä¸´æ¶æä»¶IDå表 * @throws IOException æä»¶æä½å¼å¸¸ */ private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException { public void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException { if (CollectionUtils.isEmpty(tempFileIds)) { return; } src/main/resources/application.yml
@@ -38,6 +38,14 @@ com.ruoyi: warn org.springframework: warn minio: endpoint: http://114.132.189.42/ port: 7019 secure: false accessKey: admin secretKey: 12345678 preview-expiry: 24 # é¢è§å°åé»è®¤24å°æ¶ default-bucket: uploadPath # ç¨æ·é ç½® user: password: src/main/resources/mapper/basic/StorageAttachmentMapper.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,22 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ruoyi.basic.mapper.StorageAttachmentMapper"> <!-- éç¨æ¥è¯¢æ å°ç»æ --> <resultMap id="BaseResultMap" type="com.ruoyi.basic.pojo.StorageAttachment"> <id column="id" property="id" /> <result column="create_time" property="createTime" /> <result column="update_time" property="updateTime" /> <result column="deleted" property="deleted" /> <result column="record_type" property="recordType" /> <result column="record_id" property="recordId" /> <result column="name" property="name" /> <result column="storage_blob_id" property="storageBlobId" /> </resultMap> <!-- éç¨æ¥è¯¢ç»æå --> <sql id="Base_Column_List"> id, create_time, update_time, deleted, record_type, record_id, name, storage_blob_id </sql> </mapper> src/main/resources/mapper/basic/StorageBlobMapper.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,22 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ruoyi.basic.mapper.StorageBlobMapper"> <!-- éç¨æ¥è¯¢æ å°ç»æ --> <resultMap id="BaseResultMap" type="com.ruoyi.basic.pojo.StorageBlob"> <id column="id" property="id" /> <result column="create_time" property="createTime" /> <result column="key" property="key" /> <result column="content_type" property="contentType" /> <result column="original_filename" property="originalFilename" /> <result column="bucket_filename" property="bucketFilename" /> <result column="bucket_name" property="bucketName" /> <result column="byte_size" property="byteSize" /> </resultMap> <!-- éç¨æ¥è¯¢ç»æå --> <sql id="Base_Column_List"> id, create_time, key, content_type, original_filename,bucket_filename,bucket_name, byte_size </sql> </mapper> src/main/resources/mapper/purchase/ProductRecordMapper.xml
@@ -35,5 +35,28 @@ <if test="c.createdAtEnd != null and c.createdAtEnd != ''"> and pr.created_at <= date_format(#{c.createdAtEnd},'%Y-%m-%d hh:mm:ss') </if> <if test="c.purchaseContractNumber != null and c.purchaseContractNumber != ''"> and tr.purchase_contract_number like concat('%',#{c.purchaseContractNumber},'%') </if> </select> <select id="getProductRecordById" resultType="com.ruoyi.purchase.dto.ProductRecordDto"> SELECT sl.sales_contract_no, sl.customer_contract_no, sl.customer_name, pm.model AS product_model, pl.purchase_contract_number, pl.supplier_name, pr.*, tr.invoice_number, ROUND(pr.tickets_amount/(1+pr.tax_rate/100),2 ) as un_tickets_price, ROUND(pr.tickets_amount-pr.tickets_amount/(1+pr.tax_rate/100),2 )as invoice_amount FROM product_record pr left join purchase_ledger pl on pl.id = pr.purchase_ledger_id left join sales_ledger sl on sl.id = pl.sales_ledger_id left join ticket_registration tr on tr.id = pr.ticket_registration_id left join product_model pm on pm.id = pr.product_model_id WHERE type = 2 and pr.id = #{id} </select> </mapper>