当前位置: 首页 > news >正文

SpringBoot 3.3.1 + Minio 实现极速上传和预览模式

在这里插入图片描述

统一版本管理

<properties><minio.version>8.5.10</minio.version><aws.version>1.12.737</aws.version><hutool.version>5.8.28</hutool.version>
</properties>
<!--minio -->
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>${minio.version}</version>
</dependency>
<!--aws-s3-->
<dependency><groupId>com.amazonaws</groupId><artifactId>aws-java-sdk-s3</artifactId><version>${aws.version}</version>
</dependency>
<!--hutool -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version>
</dependency>

项目配置 application-dev.yml

# 文件系统
minio: #内部地址,可以访问到内网地址endpoint: http://172.16.11.110:10087access-key: xxxxxxsecret-key: xxxxxxbucket-name: public-example-xxxxpublic-bucket-name: public-example-xxx#外网,互联网地址preview-domain: http://116.201.11.xxx:30087

创建 MinioConfig

package com.example.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.minio.MinioClient;
import lombok.AllArgsConstructor;/*** aws-s3 通用存储操作 支持所有兼容s3协议的云存储: 阿里云OSS、腾讯云COS、华为云、七牛云、,京东云、minio * @author weimeilayer@gmail.com* @date 2021年2月3日*/
@Configuration
@AllArgsConstructor
public class MinioConfig {private final MinioProperties minioProperties;@Beanpublic MinioClient minioClient() {MinioClient minioClient = MinioClient.builder().endpoint(minioProperties.getEndpoint()).credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey()).build();return minioClient;}
}

创建 MinioProperties

package com.example.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;/*** aws 配置信息bucket 设置公共读权限* @author weimeilayer@gmail.com* @date 💓💕2021年4月1日🐬🐇 💓💕*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {/*** 对象存储服务的URL*/@Schema(description = "对象存储服务的URL")private String endpoint;/*** 自定义域名*/@Schema(description = "自定义域名")private String customDomain;/*** 反向代理和S3默认支持*/@Schema(description = "反向代理和S3默认支持")private Boolean pathStyleAccess = true;/*** 应用ID*/@Schema(description = "应用ID")private String appId;/*** 区域*/@Schema(description = "区域")private String region;/*** 预览地址*/@Schema(description = "预览地址")private String previewDomain;/*** Access key就像用户ID,可以唯一标识你的账户*/@Schema(description = "Access key就像用户ID,可以唯一标识你的账户")private String accessKey;/*** Secret key是你账户的密码*/@Schema(description = "Secret key是你账户的密码")private String secretKey;/*** 默认的存储桶名称*/@Schema(description = "默认的存储桶名称")private String bucketName;/*** 公开桶名*/@Schema(description = "公开桶名")private String publicBucketName;/*** 物理删除文件*/@Schema(description = "物理删除文件")private boolean physicsDelete;/*** 最大线程数,默认: 100*/@Schema(description = "最大线程数,默认: 100")private Integer maxConnections = 100;
}

创建 MinioTemplate

package com.example.config;import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Optional;import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.util.IOUtils;import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;/*** aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS,腾讯云COS,七牛云,京东云,minio 等}* @author weimeilayer@gmail.com ✨* @date 💓💕2024年3月7日🐬🐇 💓💕*/
@Configuration
@RequiredArgsConstructor
public class MinioTemplate implements InitializingBean {private final MinioProperties ossProperties;private AmazonS3 amazonS3;/*** 创建bucket* * @param bucketName bucket名称*/@SneakyThrowspublic void createBucket(String bucketName) {if (!amazonS3.doesBucketExistV2(bucketName)) {amazonS3.createBucket((bucketName));}}/*** 获取全部bucket API Documentation</a>*/@SneakyThrowspublic List<Bucket> getAllBuckets() {return amazonS3.listBuckets();}/*** @param bucketName bucket名称 API Documentation</a>*/@SneakyThrowspublic Optional<Bucket> getBucket(String bucketName) {return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();}/*** @param bucketName bucket名称* @see <a href= Documentation</a>*/@SneakyThrowspublic void removeBucket(String bucketName) {amazonS3.deleteBucket(bucketName);}/*** 根据文件前置查询文件* * @param bucketName bucket名称* @param prefix     前缀* @param recursive  是否递归查询* @return S3ObjectSummary 列表 API Documentation</a>*/@SneakyThrowspublic List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);return new ArrayList<>(objectListing.getObjectSummaries());}/*** 获取文件外链* * @param bucketName bucket名称* @param objectName 文件名称* @param expires    过期时间 <=7* @return url*/@SneakyThrowspublic String getObjectURL(String bucketName, String objectName, Integer expires) {Date date = new Date();Calendar calendar = new GregorianCalendar();calendar.setTime(date);calendar.add(Calendar.DAY_OF_MONTH, expires);URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());return url.toString();}/*** 获取文件* * @param bucketName bucket名称* @param objectName 文件名称* @return 二进制流 API Documentation</a>*/@SneakyThrowspublic S3Object getObject(String bucketName, String objectName) {return amazonS3.getObject(bucketName, objectName);}/*** 上传文件* * @param bucketName bucket名称* @param objectName 文件名称* @param stream     文件流* @throws Exception*/public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {putObject(bucketName, objectName, stream, (long) stream.available(), "application/octet-stream");}/*** 上传文件* * @param bucketName  bucket名称* @param objectName  文件名称* @param stream      文件流* @param size        大小* @param contextType 类型* @throws Exception*/public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size,String contextType) throws Exception {byte[] bytes = IOUtils.toByteArray(stream);ObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentLength(size);objectMetadata.setContentType(contextType);ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);// 上传return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);}/*** 获取文件信息* * @param bucketName bucket名称* @param objectName 文件名称* @throws Exception API Documentation</a>*/public S3Object getObjectInfo(String bucketName, String objectName) throws Exception {return amazonS3.getObject(bucketName, objectName);}/*** 删除文件* * @param bucketName bucket名称* @param objectName 文件名称* @throws Exception*/public void removeObject(String bucketName, String objectName) throws Exception {amazonS3.deleteObject(bucketName, objectName);}@Overridepublic void afterPropertiesSet() {ClientConfiguration clientConfiguration = new ClientConfiguration();clientConfiguration.setMaxConnections(ossProperties.getMaxConnections());AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(ossProperties.getEndpoint(), ossProperties.getRegion());AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKey(),ossProperties.getSecretKey());AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);this.amazonS3 = AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration).withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider).disableChunkedEncoding().withPathStyleAccessEnabled(ossProperties.getPathStyleAccess()).build();}
}

创建Result

package com.example.utils;import java.util.HashMap;/*** 响应信息主体* @author weimeilayer@gmail.com ✨* @date 💓💕2021年6月28日 🐬🐇 💓💕*/
public class Result extends HashMap<String, Object> {private static final long serialVersionUID = 1L;/** 状态码 */public static final String CODE_TAG = "code";/** 返回内容 */public static final String MSG_TAG = "msg";/** 数据对象 */public static final String DATA_TAG = "data";/*** 初始化一个新创建的 Result 对象,使其表示一个空消息。*/public Result() {}/*** 初始化一个新创建的 Result 对象** @param code 状态码* @param msg  返回内容*/public Result(int code, String msg) {super.put(CODE_TAG, code);super.put(MSG_TAG, msg);}/*** 初始化一个新创建的 Result 对象** @param code 状态码* @param msg  返回内容* @param data 数据对象*/public Result(int code, String msg, Object data) {super.put(CODE_TAG, code);super.put(MSG_TAG, msg);if (data != null) {super.put(DATA_TAG, data);}}/*** 返回成功消息** @return 成功消息*/public static Result success() {return Result.success("操作成功");}/*** 返回成功数据** @return 成功消息*/public static Result success(Object data) {return Result.success("操作成功", data);}/*** 返回成功消息** @param msg 返回内容* @return 成功消息*/public static Result success(String msg) {return Result.success(msg, null);}/*** 返回成功消息** @param msg  返回内容* @param data 数据对象* @return 成功消息*/public static Result success(String msg, Object data) {return new Result(HttpStatus.SUCCESS, msg, data);}/*** 返回警告消息** @param msg 返回内容* @return 警告消息*/public static Result warn(String msg) {return Result.warn(msg, null);}/*** 返回警告消息** @param msg  返回内容* @param data 数据对象* @return 警告消息*/public static Result warn(String msg, Object data) {return new Result(HttpStatus.WARN, msg, data);}/*** 返回错误消息** @return 错误消息*/public static Result error() {return Result.error("操作失败");}/*** 返回错误消息** @param msg 返回内容* @return 错误消息*/public static Result error(String msg) {return Result.error(msg, null);}/*** 返回错误消息** @param msg  返回内容* @param data 数据对象* @return 错误消息*/public static Result error(String msg, Object data) {return new Result(HttpStatus.ERROR, msg, data);}/*** 返回错误消息** @param code 状态码* @param msg  返回内容* @return 错误消息*/public static Result error(int code, String msg) {return new Result(code, msg, null);}/*** 方便链式调用** @param key   键* @param value 值* @return 数据对象*/@Overridepublic Result put(String key, Object value) {super.put(key, value);return this;}
}

创建 HttpStatus

package com.example.utils;/*** http请求状态* @author weimeilayer@gmail.com ✨* @date 💓💕2024年6月28日 🐬🐇 💓💕*/
public class HttpStatus {/*** 操作成功*/public static final int SUCCESS = 200;/*** 对象创建成功*/public static final int CREATED = 201;/*** 请求已经被接受*/public static final int ACCEPTED = 202;/*** 操作已经执行成功,但是没有返回数据*/public static final int NO_CONTENT = 204;/*** 资源已被移除*/public static final int MOVED_PERM = 301;/*** 重定向*/public static final int SEE_OTHER = 303;/*** 资源没有被修改*/public static final int NOT_MODIFIED = 304;/*** 参数列表错误(缺少,格式不匹配)*/public static final int BAD_REQUEST = 400;/*** 未授权*/public static final int UNAUTHORIZED = 401;/*** 访问受限,授权过期*/public static final int FORBIDDEN = 403;/*** 资源,服务未找到*/public static final int NOT_FOUND = 404;/*** 不允许的http方法*/public static final int BAD_METHOD = 405;/*** 资源冲突,或者资源被锁*/public static final int CONFLICT = 409;/*** 不支持的数据,媒体类型*/public static final int UNSUPPORTED_TYPE = 415;/*** 系统内部错误*/public static final int ERROR = 500;/*** 接口未实现*/public static final int NOT_IMPLEMENTED = 501;/*** 系统警告消息*/public static final int WARN = 601;
}

创建 Constants

package com.example.utils;/*** 通用常量信息* @author weimeilayer@gmail.com ✨* @date 💓💕2024年6月28日 🐬🐇 💓💕*/
public class Constants {/*** UTF-8 字符集*/public static final String UTF8 = "UTF-8";/*** GBK 字符集*/public static final String GBK = "GBK";/*** www主域*/public static final String WWW = "www.";/*** http请求*/public static final String HTTP = "http://";/*** https请求*/public static final String HTTPS = "https://";/*** 通用成功标识*/public static final String SUCCESS = "0";/*** 通用失败标识*/public static final String FAIL = "1";/*** 登录成功*/public static final String LOGIN_SUCCESS = "Success";/*** 注销*/public static final String LOGOUT = "Logout";/*** 注册*/public static final String REGISTER = "Register";/*** 登录失败*/public static final String LOGIN_FAIL = "Error";/*** 验证码有效期(分钟)*/public static final Integer CAPTCHA_EXPIRATION = 2;/*** 令牌*/public static final String TOKEN = "token";/*** 令牌前缀*/public static final String TOKEN_PREFIX = "Bearer ";/*** 令牌前缀*/public static final String LOGIN_USER_KEY = "login_user_key";/*** 用户头像*/public static final String JWT_AVATAR = "avatar";/*** 创建时间*/public static final String JWT_CREATED = "created";/*** 用户权限*/public static final String JWT_AUTHORITIES = "authorities";/*** 资源映射路径 前缀*/public static final String RESOURCE_PREFIX = "/profile";/*** RMI 远程方法调用*/public static final String LOOKUP_RMI = "rmi:";/*** LDAP 远程方法调用*/public static final String LOOKUP_LDAP = "ldap:";/*** LDAPS 远程方法调用*/public static final String LOOKUP_LDAPS = "ldaps:";
}

数据库表

CREATE TABLE `sys_file` (`id` varchar(32) NOT NULL COMMENT '主键',`name` varchar(200) DEFAULT NULL COMMENT '原文件名',`group_id` varchar(32) DEFAULT NULL COMMENT '分组编号,对应多文件',`file_type` varchar(200) DEFAULT NULL COMMENT '文件类型',`suffix` varchar(200) DEFAULT NULL COMMENT '文件后缀',`size` int(11) DEFAULT NULL COMMENT '文件大小,单位字节',`preview_url` varchar(1000) DEFAULT NULL COMMENT '预览地址',`storage_type` varchar(200) DEFAULT NULL COMMENT '存储类型',`storage_url` varchar(200) DEFAULT NULL COMMENT '存储地址',`bucket_name` varchar(200) DEFAULT NULL COMMENT '桶名',`object_name` varchar(200) DEFAULT NULL COMMENT '桶内文件名',`visit_count` int(11) DEFAULT NULL COMMENT '访问次数',`sort` int(11) DEFAULT '0' COMMENT '排序值',`remarks` varchar(200) DEFAULT NULL COMMENT '备注',`gmt_create` timestamp NULL DEFAULT NULL COMMENT '创建时间',`gmt_modified` timestamp NULL DEFAULT NULL COMMENT '更新时间',`create_by` varchar(32) DEFAULT NULL COMMENT '创建人ID',`update_by` varchar(32) DEFAULT NULL COMMENT '修改人ID',`del_flag` varchar(32) DEFAULT '0' COMMENT '逻辑删除(0:未删除;null:已删除)',`tenant_id` int(11) DEFAULT NULL COMMENT '所属租户',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统基本信息--文件管理信息';

实体类 SysFile

package com.example.entity;import java.io.Serial;
import java.time.LocalDateTime;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** 系统基础信息--文件管理表* @author weimeilayer@gmail.com ✨* @date 💓💕2021年2月28日 🐬🐇 💓💕*/
@Data
@TableName("sys_file")
@EqualsAndHashCode(callSuper = false)
@JsonIgnoreProperties(ignoreUnknown = true)
@Schema(description = "系统基础信息--文件管理表")
public class SysFile extends Model<SysFile> {@Serialprivate static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.ASSIGN_ID)@Schema(description = "主键ID")private String id;/*** 原文件名*/@Schema(description = "原文件名")private String name;/*** 存储桶名称*/@Schema(description = "原始文件名")private String original;/*** 分组编号,用于对应多文件*/@Schema(description = "分组编号,用于对应多文件")private String groupId;/*** 文件类型*/@Schema(description = "文件类型")private String fileType;/*** 文件后缀*/@Schema(description = "文件后缀")private String suffix;/*** 文件大小,单位字节*/@Schema(description = "文件大小,单位字节")private Integer size;/*** 预览地址*/@Schema(description = "预览地址")private String previewUrl;/*** 存储类型*/@Schema(description = "存储类型")private String storageType;/*** 存储地址*/@Schema(description = "存储地址")private String storageUrl;/*** 桶名*/@Schema(description = "桶名")private String bucketName;/*** 桶内文件名*/@Schema(description = "桶内文件名")private String objectName;/*** 访问次数*/@Schema(description = "访问次数")private Integer visitCount;/*** 排序*/@Schema(description = "排序")private Integer sort;/*** 备注*/@Schema(description = "备注")private String remarks;/*** 逻辑删除(0:未删除;null:已删除)*/@TableLogic@Schema(description = "逻辑删除(0:未删除;null:已删除)")@TableField(fill = FieldFill.INSERT)private String delFlag;/*** 创建人*/@Schema(description = "创建人")@TableField(fill = FieldFill.INSERT)private String createBy;/*** 编辑人*/@Schema(description = "编辑人")@TableField(fill = FieldFill.UPDATE)private String updateBy;/*** 创建时间*/@TableField(fill = FieldFill.INSERT)@Schema(description = "创建时间")private LocalDateTime gmtCreate;/*** 编辑时间*/@Schema(description = "编辑时间")@TableField(fill = FieldFill.UPDATE)private LocalDateTime gmtModified;/*** 所属租户*/@Schema(description = "所属租户")private String tenantId;
}

创建接口类 SysFileService
package com.example.service;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.dto.SysFileDto;
import com.example.entity.SysFile;
import com.example.utils.Result;
import com.example.vo.SysFileSelVo;
import com.example.vo.SysFileSortVo;

import jakarta.servlet.http.HttpServletResponse;

/**

  • 系统基础信息–文件管理服务类

  • @author weimeilayer@gmail.com ✨

  • @date 💓💕 2023年5月20日 🐬🐇 💓💕
    /
    public interface SysFileService extends IService {
    /
    *

    • 上传文件
    • @param files
    • @param groupId
    • @param isPreview
    • @param isPublic
    • @param sort
    • @return
      /
      Result uploadFile(MultipartFile[] files, String groupId, Boolean isPreview, Boolean isPublic, Integer sort);
      /
      *
    • 预览
    • @param groupId
    • @return
      /
      Result preview(String groupId);
      /
      *
    • 分组预览
    • @param groupId
    • @param previewList
    • @return
      /
      boolean preview(String groupId, List previewList);
      /
      *
    • 下载
    • @param response
    • @param id
      /
      void download(HttpServletResponse response, String id);
      /
      *
    • 删除文件
    • @param id
    • @return
      /
      Result delete(String id);
      /
      *
    • 排序
    • @param vo
    • @return
      */
      Result sort(SysFileSortVo vo);

    /**

    • 分页查询SysFile
    • @param selvo 查询参数
    • @return
      /
      public IPage getSysFileDtoPage(Page page,SysFileSelVo selvo);
      /
      *
    • 上传文件
    • @param file
    • @return
      */
      public Result uploadFile(MultipartFile file);

    /**

    • 读取文件
    • @param bucket 桶名称
    • @param fileName 文件名称
    • @param response 输出流
      */
      public void getFile(String bucket, String fileName, HttpServletResponse response);

    /**

    • 删除文件
    • @param id
    • @return
      */
      public Boolean deleteFile(String id);
      }

实现类 SysFileServiceImpl

package com.example.service.impl;import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;import com.amazonaws.services.s3.model.S3Object;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.config.MinioProperties;
import com.example.config.MinioTemplate;
import com.example.dto.SysFileDto;
import com.example.dto.SysFileSelDto;
import com.example.entity.SysFile;
import com.example.mapper.SysFileMapper;
import com.example.service.SysFileService;
import com.example.utils.Result;
import com.example.vo.SysFileSelVo;
import com.example.vo.SysFileSortVo;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
/*** 系统基础信息--文件管理服务实现类** @author weimeilayer@gmail.com ✨* @date 💓💕 2023年5月20日 🐬🐇 💓💕*/
@Service
@AllArgsConstructor
public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements SysFileService {private final MinioClient minioClient;private final MinioTemplate minioTemplate;private final MinioProperties minioProperties;/*** 上传文件** @param file* @return*/@Overridepublic Result uploadFile(MultipartFile file) {String fileId = IdUtil.simpleUUID();String originalFilename = new String(Objects.requireNonNull(file.getOriginalFilename()).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);String fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(originalFilename);Map<String, String> resultMap = new HashMap<>(4);resultMap.put("bucketName", minioProperties.getBucketName());resultMap.put("fileName", fileName);resultMap.put("originalFilename", originalFilename);resultMap.put("fileId", fileId);resultMap.put("url", String.format("/sysfile/%s/%s", minioProperties.getBucketName(), fileName));try (InputStream inputStream = file.getInputStream()) {minioTemplate.putObject(minioProperties.getBucketName(), fileName, inputStream, file.getSize(), file.getContentType());// 文件管理数据记录,收集管理追踪文件fileLog(file, fileName, fileId);} catch (Exception e) {log.error("上传失败", e);return Result.error(e.getLocalizedMessage());}return Result.success(resultMap);}/*** 读取文件** @param bucket* @param fileName* @param response*/@Overridepublic void getFile(String bucket, String fileName, HttpServletResponse response) {try (S3Object s3Object = minioTemplate.getObject(bucket, fileName)) {response.setContentType("application/octet-stream; charset=UTF-8");IoUtil.copy(s3Object.getObjectContent(), response.getOutputStream());} catch (Exception e) {Console.log("文件读取异常: {}", e.getLocalizedMessage());}}/*** 删除文件** @param id* @return*/@Override@SneakyThrows@Transactional(rollbackFor = Exception.class)public Boolean deleteFile(String id) {SysFile file = this.getById(id);minioTemplate.removeObject(minioProperties.getBucketName(), file.getName());return file.updateById();}/*** 文件管理数据记录,收集管理追踪文件** @param file     上传文件格式* @param fileName 文件名*/private void fileLog(MultipartFile file, String fileName, String fileId) {String originalFilename = new String(Objects.requireNonNull(file.getOriginalFilename()).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);SysFile sysFile = new SysFile();sysFile.setId(fileId);sysFile.setName(fileName);sysFile.setOriginal(originalFilename);sysFile.setSize((int) file.getSize());sysFile.setFileType(FileUtil.extName(file.getOriginalFilename()));sysFile.setBucketName(minioProperties.getBucketName());this.save(sysFile);}/*** 分页查询SysFile* @param page* @param selvo 查询参数* @return*/@Overridepublic IPage<SysFileDto> getSysFileDtoPage(Page page, SysFileSelVo selvo) {return baseMapper.getSysFileDtoPage(page, selvo);}@Overridepublic Result uploadFile(MultipartFile[] files, String groupId, Boolean isPreview, Boolean isPublic, Integer sort) {if (files == null || files.length == 0) {return Result.error("上传文件不能为空!");}// 是否公开isPublic = isPublic != null && isPublic;// 是否预览isPreview = isPreview != null && isPreview;// 桶名String bucketName = isPublic ? minioProperties.getPublicBucketName() : minioProperties.getBucketName();// 文件目录String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/"));// 预览列表List<SysFileSelDto> previewList = new ArrayList<>();// 分组编号,用于对应多文件if (StringUtils.hasText(groupId)) {// 排序if (sort == null) {sort = baseMapper.getMaxSort(groupId);if (sort != null) {sort++;} else {sort = 0;}}} else {groupId = IdUtil.simpleUUID();sort = 0;}for (int i = 0; i < files.length; i++) {MultipartFile file = files[i];InputStream in = null;try {// 原文件名String oriFileName = new String(file.getOriginalFilename().getBytes("ISO-8859-1"), "UTF-8");// 后缀String suffix = "";if (StringUtils.hasText(oriFileName)) {int index = oriFileName.lastIndexOf(StrPool.DOT);if (index != -1) {suffix = oriFileName.substring(index + 1);}}// minio文件名String objectName = dir + IdUtil.simpleUUID() + StrPool.DOT + suffix;in = file.getInputStream();// 上传文件minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(in, file.getSize(), -1).contentType(file.getContentType()).build());long size = file.getSize();String id = IdUtil.simpleUUID();String previewUrl = null;if (isPreview) {// 返回预览地址previewUrl = getPreviewUrl(bucketName, objectName);if (!StringUtils.hasText(previewUrl)) {continue;}// 去掉后缀if (isPublic) {previewUrl = previewUrl.substring(0, previewUrl.indexOf("?"));}previewList.add(new SysFileSelDto(id, oriFileName, suffix, formatFileSize(size), previewUrl, i));}// minio文件信息插入数据库minioInsertToDb(id, oriFileName, groupId, file.getContentType(), suffix, (int) size, bucketName, objectName, previewUrl, i + sort);} catch (Exception e) {log.error(e.getMessage());return Result.error("上传失败!");} finally {if (in != null) {try {in.close();} catch (IOException e) {log.error(e.getMessage());}}}}return Result.success("上传成功!",isPreview ? previewList : groupId);}@Overridepublic Result preview(String groupId) {List<SysFileSelVo> previewList = new ArrayList<>();boolean preview = preview(groupId, previewList);return preview ? Result.success(previewList) : Result.error("预览失败!");}/*** 文件下载*/@Overridepublic void download(HttpServletResponse response, String id) {SysFile sysFile = baseMapper.selectOne(Wrappers.<SysFile>lambdaQuery().select(SysFile::getBucketName, SysFile::getObjectName, SysFile::getName).eq(SysFile::getDelFlag, 0).eq(SysFile::getId, id));if (sysFile == null) {return;}String objectName = sysFile.getObjectName();if (CharSequenceUtil.isBlank(objectName)) {return;}InputStream in = null;try {String bucketName = sysFile.getBucketName();// 获取对象信息StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());response.setContentType(stat.contentType());response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(sysFile.getName(), "UTF-8"));// 文件下载in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());IoUtil.copy(in, response.getOutputStream());} catch (Exception e) {log.error(e.getMessage());} finally {if (in != null) {try {in.close();} catch (IOException e) {log.error(e.getMessage());}}}}@Overridepublic Result delete(String id) {SysFile sysFile = baseMapper.selectOne(Wrappers.<SysFile>lambdaQuery().select(SysFile::getId, SysFile::getBucketName, SysFile::getObjectName).eq(SysFile::getDelFlag, 0).eq(SysFile::getId, id));if (sysFile == null) {return Result.error("未找到文件!");}String objectName = sysFile.getObjectName();if (CharSequenceUtil.isBlank(objectName)) {return Result.error("未找到文件!");}// 数据库删除文件int update = baseMapper.update(null, Wrappers.<SysFile>lambdaUpdate().set(SysFile::getDelFlag, null).set(SysFile::getGmtModified, LocalDateTime.now()).set(SysFile::getUpdateBy, sysFile.getId()).eq(SysFile::getId, id));if (update == 0) {Result.error("删除失败!");}// 是否物理删除minio上文件if (minioProperties.isPhysicsDelete()) {try {minioClient.removeObject(RemoveObjectArgs.builder().bucket(sysFile.getBucketName()).object(objectName).build());// minio文件信息数据库逻辑删除minioDeleteToDb(objectName);} catch (Exception e) {log.error(e.getMessage());return Result.error("删除失败!");}}return Result.success("删除成功!");}@Overridepublic Result sort(SysFileSortVo vo) {String id = vo.getId();Integer sort = vo.getSort();if (!StringUtils.hasText(id) || sort == null) {return Result.error("参数错误!");}SysFile sysFile = new SysFile();sysFile.setId(id);sysFile.setSort(sort);sysFile.updateById();return Result.success("编辑成功!");}/*** 文件大小处理** @param fileSize 文件大小,单位B* @param fileSize* @return*/private String formatFileSize(long fileSize) {DecimalFormat df = new DecimalFormat("#.00");String fileSizeizeString;String wrongSize = "0B";if (fileSize == 0) {return wrongSize;}if (fileSize < 1024) {fileSizeizeString = df.format((double) fileSize) + " B";} else if (fileSize < 1048576) {fileSizeizeString = df.format((double) fileSize / 1024) + " KB";} else if (fileSize < 1073741824) {fileSizeizeString = df.format((double) fileSize / 1048576) + " MB";} else {fileSizeizeString = df.format((double) fileSize / 1073741824) + " GB";}return fileSizeizeString;}/*** 获取预览地址路径** @param bucketName 桶名* @param objectName minio文件名*/private String getPreviewUrl(String bucketName, String objectName) {String previewUrl = null;try {// 预览地址previewUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectName)// 24小时,默认7天.expiry(60 * 60 * 24).expiry(15).build());if (StrUtil.isNotBlank(minioProperties.getPreviewDomain())) {int count = 0;int index = -1;for (int i = 0; i < previewUrl.length(); i++) {if (previewUrl.charAt(i) == '/') {count++;if (count == 3) {index = i;break;}}}if (index != -1) {previewUrl = minioProperties.getPreviewDomain() + previewUrl.substring(index);}}} catch (Exception e) {Console.log(e.getMessage());}return previewUrl;}/*** minio文件信息插入数据库** @param id         主键* @param name       原文件名* @param groupId    分组编号,用于对应多文件* @param fileType   fileType* @param suffix     suffix* @param size       文件大小,单位字节* @param objectName 桶内文件名*/private void minioInsertToDb(String id, String name, String groupId, String fileType, String suffix, Integer size, String bucketName, String objectName, String previewUrl, int sort) {SysFile sysFile = new SysFile();sysFile.setId(id);sysFile.setName(name);sysFile.setGroupId(groupId);sysFile.setFileType(fileType);sysFile.setSuffix(suffix);sysFile.setSize(size);sysFile.setStorageType("minio");sysFile.setBucketName(bucketName);sysFile.setObjectName(objectName);sysFile.setVisitCount(0);sysFile.setPreviewUrl(previewUrl);sysFile.setSort(sort);baseMapper.insert(sysFile);}/*** minio文件信息数据库逻辑删除** @param objectName 桶内文件名*/private void minioDeleteToDb(String objectName) {SysFile sysFile = baseMapper.selectOne(Wrappers.<SysFile>lambdaQuery().select(SysFile::getId).eq(SysFile::getObjectName, objectName).eq(SysFile::getDelFlag, 0));if (sysFile != null) {baseMapper.update(null, Wrappers.<SysFile>lambdaUpdate().set(SysFile::getDelFlag, null).set(SysFile::getGmtModified, LocalDateTime.now()).eq(SysFile::getId, sysFile.getDelFlag()));}}/*** 预览** @param groupId 分组id*/@Overridepublic boolean preview(String groupId, List<SysFileSelVo> previewList) {List<SysFile> sysFiles = baseMapper.selectList(Wrappers.<SysFile>lambdaQuery().select(SysFile::getId, SysFile::getName, SysFile::getBucketName, SysFile::getObjectName, SysFile::getSuffix, SysFile::getSize, SysFile::getSort).eq(SysFile::getDelFlag, 0).eq(SysFile::getGroupId, groupId).orderByAsc(SysFile::getSort));if (CollUtil.isEmpty(sysFiles)) {return false;}for (SysFile sysFile : sysFiles) {try {// 预览地址String previewUrl = getPreviewUrl(sysFile.getBucketName(), sysFile.getObjectName());// 文件大小并格式化String size = formatFileSize(sysFile.getSize());previewList.add(new SysFileSelVo(sysFile.getId(), sysFile.getName(), sysFile.getSuffix(), size, previewUrl, sysFile.getSort()));} catch (Exception e) {Console.log(e.getMessage());}}return true;}
}

创建SysFileMapper

package com.example.mapper;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cqcloud.platform.common.data.datascope.DynamicBaseMapper;
import com.cqcloud.platform.dto.SysFileDto;
import com.cqcloud.platform.entity.SysFile;
import com.cqcloud.platform.vo.SysFileSelVo;/*** 系统基础信息--文件管理信息 Mapper 接口* @author weimeilayer@gmail.com ✨* @date 💓💕 2021年5月20日 🐬🐇 💓💕*/
@Mapper
public interface SysFileMapper extends BaseMapper<SysFile> {/*** 排序* @param groupId* @return*/public Integer getMaxSort(@Param("groupId") String groupId);/*** 分页查询SysFile* @param selvo 查询参数* @return*/public IPage<SysFileDto> getSysFileDtoPage(@Param("page")Page page,@Param("query")SysFileSelVo selvo);
}

创建 SysFileMapper.xml

<?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.cqcloud.platform.mapper.SysFileMapper"><resultMap id="sysFileMap" type="com.example.dto.SysFileDto" ><result property="id" 				column="id"/><result property="name" 			column="name"/><result property="groupId" 			column="group_id"/><result property="fileType" 		column="file_type"/><result property="suffix" 			column="suffix"/><result property="size" 			column="size"/><result property="previewUrl" 		column="preview_url"/><result property="storageType" 		column="storage_type"/><result property="storageUrl" 		column="storage_url"/><result property="bucketName" 		column="bucket_name"/><result property="objectName" 		column="object_name"/><result property="visitCount" 		column="visit_count"/><result property="sort" 			column="sort"/><result property="remarks" 			column="remarks"/><result property="gmtCreate" 		column="gmt_create"/><result property="gmtModified" 		column="gmt_modified"/><result property="createBy" 		column="create_by"/><result property="updateBy" 		column="update_by"/><result property="delFlag" 			column="del_flag"/><result property="tenantId" 		column="tenant_id"/></resultMap><sql id="sysFileSql">
t.id,t.name,t.group_id,t.file_type,t.suffix,t.size,t.preview_url,t.storage_type,t.storage_url,t.bucket_name,t.object_name,t.visit_count,t.sort,t.remarks,t.gmt_create,t.gmt_modified,t.create_by,t.update_by,t.del_flag,t.tenant_id</sql><select id="getSysFileDtoPage" resultMap="sysFileMap">select <include refid="sysFileSql" />from sys_file t<where>t.del_flag='0'<if test="query.name !=null and query.name !=''">and t.name LIKE '%' || #{name} || '%'</if></where>order by t.gmt_create desc</select><select id="getMaxSort" resultType="java.lang.Integer">selectmax(sort)fromsys_filewheregroup_id = #{groupId}and del_flag = '0'</select>
</mapper>

创建 SysFileController

package com.example.controller;import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cqcloud.platform.common.log.annotation.SysLog;
import com.cqcloud.platform.service.SysFileService;
import com.cqcloud.platform.utils.Result;
import com.cqcloud.platform.vo.SysFileSelVo;
import com.cqcloud.platform.vo.SysFileSortVo;import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;/*** 系统基础信息--文件管理模块* @author weimeilayer@gmail.com* @date 2021-12-13 16:28:32*/
@RestController
@AllArgsConstructor
@RequestMapping("/sysfile")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class SysFileController {private final SysFileService sysFileService;/*** 上传文件 文件名采用uuid,避免原始文件名中带"-"符号导致下载的时候解析出现异常* @param file 资源* @return R(/bucketName/filename)*/@PostMapping("/uploadOnToken")public Result upload(@RequestParam("file") MultipartFile file) {if (file.isEmpty()) {return Result.error("文件上传失败");}return sysFileService.uploadFile(file);}/*** 获取文件* * @param bucket   桶名称* @param fileName 文件空间/名称* @param response* @return*/@GetMapping("/{bucket}/{fileName}")public void file(@PathVariable String bucket, @PathVariable String fileName, HttpServletResponse response) {sysFileService.getFile(bucket, fileName, response);}/*** 分页查询文件信息列表* @param page* @return*/@GetMapping("/pagelist")public Result getSysFileDtoPage(@ParameterObject Page page,@ParameterObject SysFileSelVo selvo) {return Result.success(sysFileService.getSysFileDtoPage(page, selvo));}/*** 上传文件* @param file    多文件* @param groupId 分组id,用于文件追加*/@PostMapping("/upload")@Parameters({@Parameter(name = "groupId", description = "分组编号,用于对应多文件",example = "1"),@Parameter(name = "isPreview", description = "是否预览", required = true,example = "1"),@Parameter(name = "isPublic", description = "是否公开", required = true,example = "1"),@Parameter(name = "sort", description = "排序", required = true,example = "1")})public Result upload(@RequestParam MultipartFile[] file, String groupId, Boolean isPreview, Boolean isPublic,Integer sort) {return sysFileService.uploadFile(file, groupId, isPreview, isPublic, sort);}/*** 批量预览文件* @param groupId 文件名*/@GetMapping("/preview/{groupId}")public Result preview(@PathVariable("groupId") String groupId) {return sysFileService.preview(groupId);}/*** 下载文件* @param id 主键*/@GetMapping("/download/{id}")public void download(HttpServletResponse response, @PathVariable("id") String id) {sysFileService.download(response, id);}/*** 删除文件* @param id 主键*/@DeleteMapping("/delete/{id}")public Result delete(@PathVariable("id") String id) {return sysFileService.delete(id);}/*** 文件排序* @param vo 排序封装*/@PostMapping("/sort")public Result sort(@RequestBody SysFileSortVo vo) {return sysFileService.sort(vo);}
}

相关文章:

SpringBoot 3.3.1 + Minio 实现极速上传和预览模式

统一版本管理 <properties><minio.version>8.5.10</minio.version><aws.version>1.12.737</aws.version><hutool.version>5.8.28</hutool.version> </properties><!--minio --> <dependency><groupId>io.m…...

Linux: network: 丢包分析的另一个途径 tracing

丢包的另一个思路&#xff0c;内核里有些counter的计数&#xff0c;记录的不准确。这个时候怎么办&#xff1f;就需要使用另外一个方式&#xff1a;/sys/kernel/debug/tracing/event/skb/kfree_skb 的跟踪功能。这个算是对counter的一个补充&#xff0c;可以拿来做统计分析使用…...

【保姆级教程+配置源码】在VScode配置C/C++环境

目录 一、下载VScode 1. 在官网直接下载安装即可 2. 安装中文插件 二、下载C语言编译器MinGW-W64 三、配置编译器环境变量 1. 解压下载的压缩包&#xff0c;复制该文件夹下bin目录所在地址 2. 在电脑搜索环境变量并打开 3. 点击环境变量→选择系统变量里的Path→点击编…...

Qt creator实现一个简单计算器

目录 1 界面设计 2 思路简介 3 代码 目录 1 界面设计 ​2 思路简介 3 代码 3.1 widget.h 3.2 widget.c 4 完整代码 在这里主要记载了如何使用Qt creator完成一个计算器的功能。该计算器可以实现正常的加减乘除以及括号操作&#xff0c;能实现简单的计算器功能。 1 界…...

Java代码基础算法练习-计算被 3 或 5 整除数之和-2024.06.29

任务描述&#xff1a; 计算 1 到 n 之间能够被 3 或者 5 整除的数之和。 解决思路&#xff1a; 输入的数字为 for 循环总次数&#xff0c;每次循环就以当前的 i 进行 3、5 的取余操作&#xff0c;都成立计入总数sum中&#xff0c;循环结束&#xff0c;输出 sum 的值 代码示例&…...

Socket编程详解(二)核心代码讲解

本文对代码的讲解基于上一篇博客 快速链接 Socket编程详解&#xff08;一&#xff09;服务端与客户端的双向对话 小试牛刀1&#xff1a;委托声明的关键字和委托方法使用的方法名是不一样的名称 可读性&#xff1a;有时&#xff0c;委托的名称可能描述了它的用途或它在哪里被…...

(项目实战)聚合支付系统开发环境搭建-基于VMware17安装Centos7.9

1 开发环境介绍 dtpay聚合支付系统和ecard预付卡系统&#xff0c;服务端部署在Linux环境。后续的开发环境&#xff0c;生产环境都是基于Linux进行搭建&#xff0c;系统使用到的相关中间件(RocketMQ,Redis&#xff0c;Nginx等)&#xff0c;配置中心Nacos&#xff0c;数据库MySQ…...

Python现在可以在线编程了!

你好&#xff0c;我是郭震 1 在线编程 在线编程好处&#xff1a; 1 无需安装和配置环境: 在线编程平台不需要用户在本地安装任何软件或配置开发环境。这对初学者和那些希望快速上手进行编程的人非常有利。 2 跨平台兼容性: 这些平台可以在任何具有互联网连接的设备上使用&#…...

ThreadPoolExecutor线程池创建线程

线程池介绍 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时&#xff0c;任务可以不需要等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源&#xff0c;如果无限制的创建&#xff0c;不仅会消耗系统资源&#…...

畅谈GPT-5

前言 ChatGBT(Chat Generative Bidirectional Transformer)是一种基于自然语言处理技术的对话系统,它的出现是人工智能和自然语言处理技术发展的必然趋势。随着技术的更新和进步&#xff0c;GPT也迎来了一代代的更新迭代。 1.GPT的回顾 1.1 GPT-3的介绍 GPT-3&#xff08;Gen…...

石家庄高校大学智能制造实验室数字孪生可视化系统平台项目验收

智能制造作为未来制造业的发展方向&#xff0c;已成为各国竞相发展的重点领域。石家庄高校大学智能制造实验室积极响应国家发展战略&#xff0c;结合自身优势&#xff0c;决定引进数字孪生技术&#xff0c;构建一个集教学、科研、生产于一体的可视化系统平台。 数字孪生可视化…...

WLAN 4-Way Handshake如何生成GTK?

关于Wi-Fi的加密认证过程&#xff0c;可以参考如下链接&#xff0c;今天我们来理解如何生成GTK。 WLAN数据加密机制_tls加密wifi-CSDN博客 1 GTK GTK&#xff08;Group Temporal Key&#xff09;是由AP通过GMK生成&#xff0c;长度为128位&#xff0c;并在四次握手的第三步中…...

Qt/C++模拟鼠标键盘输入

1、控制鼠标移动 &#xff08;1&#xff09;Qt方案 QScreen* sc QGuiApplication::primaryScreen(); QCursor* c new QCursor(); int deltaX 10; int deltaY 10; c->setPos(sc, c->pos().x() deltaX, c->pos().y() deltaY);&#xff08;2&#xff09;Windows原…...

OpenGL3.3_C++_Windows(22)

材质&#xff1a; 决定物体在渲染过程中最终视觉呈现的关键因素之一&#xff0c;它通过一系列光学&#xff08;投光物&#xff09;和物理参数&#xff08;反光度&#xff0c;反照率、金属度&#xff0c;折射率……&#xff09;准确模拟现实世界中的材料特性&#xff0c;从而增…...

electron-builder 打包过慢解决

报错内容如下 > 6-241.0.0 build > electron-builder • electron-builder version24.13.3 os10.0.22631 • loaded configuration filepackage.json ("build" field) • writing effective config filedist\builder-effective-config.yaml • pack…...

leetcode226反转二叉树

本文主要讲解反转二叉树的要点与细节&#xff0c;按照步骤思考更方便理解 c和java代码如下&#xff0c;末尾 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 具体要点&#xff1a; 1. 首先我们要理解题意&#xff0c; 反转二叉树具体…...

【自然语言处理系列】探索NLP:使用Spacy进行分词、分句、词性标注和命名实体识别,并以《傲慢与偏见》与全球恐怖活动两个实例文本进行分析

本文深入探讨了scaPy库在文本分析和数据可视化方面的应用。首先&#xff0c;我们通过简单的文本处理任务&#xff0c;如分词和分句&#xff0c;来展示scaPy的基本功能。接着&#xff0c;我们利用scaPy的命名实体识别和词性标注功能&#xff0c;分析了Jane Austen的经典小说《傲…...

【Rust】function和methed的区别

文章目录 functionmethedAssociated Functions 参考资料 一句话总结&#xff1a; function和methed很多都是相同的。 不同点在于&#xff1a; methed定义在结构体里面&#xff0c;并且它的第一个参数肯定是self&#xff0c;代表结构体实例。方法需要用实例名.方法名调用当然结…...

python基础语法 003-4 数据类型集合

1 集合 1.1 什么是集合 什么是集合&#xff1f;ANS:集合set是一个无序的不重复元素序列集合怎么表示&#xff1f;ANS: {} , 用逗号隔开打印元组类型&#xff0c;type()一个元素的集合怎么表示&#xff1f;&#xff1a;ANS:存储多种类型{"a", 1} """…...

Vue如何引用组件

在 Vue.js 中&#xff0c;你可以通过几种方式引用组件&#xff1a; 全局注册 在 main.js 或你的主入口文件中&#xff0c;你可以使用 Vue.component() 方法来全局注册一个组件。这意味着这个组件可以在你的 Vue 应用的任何地方使用。 import MyComponent from ./components/…...

vue3中省市区联动在同一个el-form-item中咋么设置rules验证都不为空的效果

在开发中出现如下情况&#xff0c;在同一个el-form-item设置了省市区三级联动的效果 <el-form-item label"地区" prop"extraProperties.Province"><el-row :gutter"20"><el-col :span"12"><el-select v-model&qu…...

如何集成CppCheck到visual studio中

1.CPPCheck安装 在Cppcheck官方网站下载最新版本1.70&#xff0c;官网链接&#xff1a;http://cppcheck.sourceforge.net/ 安装Cppcheck 2.集成步骤 打开VS&#xff0c;菜单栏工具->外部工具->添加&#xff0c;按照下图设置&#xff0c;记得勾选“使用输出窗口” 2.…...

GWO-CNN-SVM,基于GWO灰狼优化算法优化卷积神经网络CNN结合支持向量机SVM数据分类(多特征输入多分类)

GWO-CNN-SVM&#xff0c;基于GWO灰狼优化算法优化卷积神经网络CNN结合支持向量机SVM数据分类(多特征输入多分类) 1. GWO灰狼优化算法 灰狼优化算法&#xff08;Grey Wolf Optimizer, GWO&#xff09;是一种启发式优化算法&#xff0c;模拟了灰狼群体的社会行为&#xff0c;包…...

Go-知识测试-工作机制

Go-知识测试-工作机制 生成test的maintest的main如何启动case单元测试 runTeststRunnertesting.T.Run 示例测试 runExamplesrunExampleprocessRunResult 性能测试 runBenchmarksrunNtesting.B.Run 在 Go 语言的源码中&#xff0c;go test 命令的实现主要在 src/cmd/go/internal…...

【小程序静态页面】猜拳游戏大转盘积分游戏小程序前端模板源码

猜拳游戏大转盘积分游戏小程序前端模板源码&#xff0c; 一共五个静态页面&#xff0c;首页、任务列表、大转盘和猜拳等五个页面。 主要是通过做任务来获取积分&#xff0c;积分可以兑换商品&#xff0c;也可用来玩游戏&#xff1b;通过玩游戏既可能获取奖品或积分也可能会消…...

JupyterServer配置

1. 安装jupyter ​pip install jupyter -i https://pypi.tuna.tsinghua.edu.cn/simple --default-timeout1000 2. 生成配置 jupyter notebook --generate-config 3. 修改配置&#xff0c;设置密码 获取密码的方式&#xff1a;命令行输入python后&#xff0c;用以下方式获…...

信息检索(57):MINIMIZING FLOPS TO LEARN EFFICIENT SPARSE REPRESENTATIONS

MINIMIZING FLOPS TO LEARN EFFICIENT SPARSE REPRESENTATIONS 摘要1 引言2 相关工作3 预期 FLOPS 次数4 我们的方法5 实验6 结论 发布时间&#xff08;2020&#xff09; 最小化 Flop 来学习高效的稀疏表示 摘要 1&#xff09;学习高维稀疏表示 2&#xff09;FLOP 集成到损失…...

Python 面试【中级】

欢迎莅临我的博客 &#x1f49d;&#x1f49d;&#x1f49d;&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…...

[Open-source tool]Uptime-kuma的簡介和安裝於Ubuntu 22.04系統

[Uptime Kuma]How to Monitor Mqtt Broker and Send Status to Line Notify Uptime-kuma 是一個基於Node.js的開軟軟體&#xff0c;同時也是一套應用於網路監控的開源軟體&#xff0c;其利用瀏覽器呈現直觀的使用者介面&#xff0c;如圖一所示&#xff0c;其讓使用者可監控各種…...

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 灰度图像恢复(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…...

wordpress标签插件/app开发工具

lotus同一台机拆分P1、P2绑定CPU创建目录运行P1&#xff0c;绑定cpu 0-15运行P2&#xff0c;绑定cpu 16-31创建目录 /tmp01 /tmp02/seal/worker01 /seal/worker02运行P1&#xff0c;绑定cpu 0-15 env TMPDIR/tmp01 nohup taskset -c 0-15 lotus-worker --worker-repo/seal/wo…...

禅城区企业网站建设/灰色关键词排名优化

基于虎书实现LALR(1)分析并生成GLSL编译器前端代码(C#) 为了完美解析GLSL源码&#xff0c;获取其中的信息&#xff08;都有哪些in/out/uniform等&#xff09;&#xff0c;我决定做个GLSL编译器的前端&#xff08;以后简称编译器或FrontEndParser&#xff09;。 以前我做过一个…...

html 单页网站/百度霸屏培训

部署django项目常见的方式有三种: 1.在windows服务器中&#xff0c;采用IIS服务器进行部署。 2.在Linux服务器中&#xff0c;采用uwsginginx或者uwsgiapache的组合进行部署。 本文主要介绍在ubuntu20.04中采用uwsginginx的组合进行上线部署的方法。 第一步 安装uwsgi 1.首先启…...

网站如何做微信登录/全国seo公司排名

文章目录需求覆盖率代码覆盖率代码覆盖率的价值代码覆盖率的局限性代码覆盖率工具代码覆盖率工具的实现原理第一&#xff0c;On-The-Fly 注入模式第二&#xff0c;Offline 注入模式总结测试覆盖率通常被用来衡量测试的充分性和完整性&#xff0c;从广义的角度来讲&#xff0c;测…...

培训销售网站建设/免费b2b推广网站

一.先以非授权的模式启动MongoDB非授权&#xff1a; linux/Mac : mongod -f /mongodb/etc/mongo.conf windows : mongod --config c:\mongodb\etc\mongo.conf 或者 net start mongodb &#xff08;前提是mongo安装到了服务里面&#xff09; 备注&#xff1a; /mongodb/etc/mong…...

泰州市建设监理协会网站/网络营销活动案例

作为一只linux菜的不能更菜的鸟&#xff0c;在linux前台运行了一个服务后&#xff0c;我必须退出后才能执行其他命令&#xff0c;可是我的服务需要一直运行&#xff0c;于是我同事就给我在该命令后加了">/dev/null 2>&1"据说是这条命令就可以转向后台了&am…...