当前位置: 首页 > 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…...

leetcode494. 目标和

1.思想方法 2.代码 class Solution { public int findTargetSumWays(int[] nums, int target) {int sum 0;for(int num : nums)sum num;if(sum < Math.abs(target) || (targetsum)%2 ! 0)return 0;int x (targetsum) / 2,n nums.length;//基于滚动数组的方法int[] dp…...

数据结构简介

在容器的基础之上&#xff0c;java引入了数据结构的概念。数据结构可以简单地理解成是一个以特定的布局方式来存储数据的容器。但是我个人觉得这种理解方式不太合理&#xff0c;根据我们学的数据结构的内容&#xff0c;我更倾向于数据结构是数据在容器中的布局方式&#xff0c;…...

PyScript:在浏览器中释放Python的强大

PyScript&#xff1a;Python代码&#xff0c;直接在网页上运行。- 精选真开源&#xff0c;释放新价值。 概览 PyScript是一个创新的框架&#xff0c;它打破了传统编程环境的界限&#xff0c;允许开发者直接在浏览器中使用Python语言来创建丰富的网络应用。结合了HTML界面、Pyo…...

巴黎成为欧洲AI中心 大学开始输出AI创始人

来自Dealroom 的数据显示&#xff0c;在欧洲和以色列AI创业公司中&#xff0c;法国的AI创业公司资金最充裕。Mistral、Owkin、Hugging Face等法国企业已经融资23亿美元&#xff0c;比英国、德国AI创业公司都要多。 一名大学生走出校门凭借聪明才智和一个黄金点子成为富豪&#…...

完全离线的本地问答模型LocalGPT如何实现无公网IP远程连接提问

文章目录 前言环境准备1. localGPT部署2. 启动和使用3. 安装cpolar 内网穿透4. 创建公网地址5. 公网地址访问6. 固定公网地址 前言 本文主要介绍如何本地部署LocalGPT并实现远程访问&#xff0c;由于localGPT只能通过本地局域网IP地址端口号的形式访问&#xff0c;实现远程访问…...

【算法专题--栈】栈的压入、弹出序列 -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 &#x1f4a7;栈模拟法&#x1f4a7;-- 双指针 ⭐ 解题思路 ⭐ 案例图解 四、总结与提炼 五、共勉 一、前言 栈的压入、弹出序列 这道题&#xff0c;可以说是--栈专题--&#xff0c;最经典的一道题&#xff0c;也是在…...

如何高效安全的开展HPC数据传输,保护数据安全?

高性能计算&#xff08;HPC&#xff09;在多个行业和领域中都有广泛的应用&#xff0c;像科学研究机构、芯片IC设计企业、金融、生物制药、能源、航天航空等。HPC&#xff08;高性能计算&#xff09;环境中的数据传输是一个关键环节&#xff0c;它涉及到将数据快速、安全地在不…...

Java部分复习笔记整理

一、Java常用类 1.String类 表示字符串&#xff0c;不可变&#xff0c;常用方法包括length(), charAt(), substring(), indexOf(), equals()等。 2.ArrayList类 基于数组实现的动态数组&#xff0c;可变大小&#xff0c;常用方法包括add(), get(), set(), remove(), size()…...

GoLang语言

基础 安装Go扩展 go build 在项目目录下执行go build go run 像执行脚本文件一样执行Go代码 go install go install分为两步&#xff1a; 1、 先编译得到一个可执行文件 2、将可执行文件拷贝到GOPATH/bin Go 命令 go build :编译Go程序 go build -o "xx.exe"…...

ctfshow web入门 sqli-labs web517--web524

web517 注入点id ?id-1’union select 1,2,3– 确认是否能够注入 ?id-1union select 1,database(),3-- 爆出库名 security爆出表名 ?id-1union select 1,(select group_concat(table_name) from information_schema.tables where table_schemasecurity),3-- emails,refer…...

Spring Cloud Gateway 跨域配置和跨服务请求跟踪

文章目录 引言I Spring Cloud Gateway 跨域配置1.1 网关统一处理:配置文件-推荐1.2 网关统一处理:配置类方式1.3 微服务处理,网关侧不用处理CORS。1.4 子服务依赖配置1.5 网关服务的依赖配置II 跨服务请求日志跟踪2.1 feign 依赖配置2.2 feign子模块将请求头中的参数,全部作…...

动手学深度学习(Pytorch版)代码实践 -卷积神经网络-29残差网络ResNet

29残差网络ResNet import torch from torch import nn from torch.nn import functional as F import liliPytorch as lp import matplotlib.pyplot as plt# 定义一个继承自nn.Module的残差块类 class Residual(nn.Module):def __init__(self, input_channels, num_chan…...

解锁音乐潮流:使用TikTok API获取平台音乐信息

一、引言 TikTok&#xff0c;作为全球领先的短视频社交平台&#xff0c;不仅为用户提供了展示自我、分享生活的舞台&#xff0c;还为用户带来了丰富多样的音乐体验。在TikTok上&#xff0c;音乐与视频内容的结合&#xff0c;为用户带来了全新的视听盛宴。对于音乐制作人、品牌…...

基于yolo的物体识别坐标转换

一、模型简介: 1.1、小孔成像模型简图如下:不考虑实际相机中存在的场曲、畸变等问题 相对关系为: 为了表述与研究的方便,我们将像面至于小孔之前,且到小孔的距离仍然是焦距f,这样的模型与原来的小孔模型是等价的 相对关系为: 二、坐标系简介: **世界坐标系(world coo…...

STM32第七课:KQM6600空气质量传感器

文章目录 需求一、KQM6600模块及接线方法二、模块配置流程1.环境2.配置时钟和IO3.配置串口初始化&#xff0c;使能以及中断4.中断函数 三、数据处理四、关键代码总结 需求 能够在串口实时显示当前的VOC&#xff08;挥发性有机化合物&#xff09;&#xff0c;甲醛和Co2浓度。 …...

任务4.8.4 利用Spark SQL实现分组排行榜

文章目录 1. 任务说明2. 解决思路3. 准备成绩文件4. 采用交互式实现5. 采用Spark项目实战概述&#xff1a;使用Spark SQL实现分组排行榜任务背景任务目标技术选型实现步骤1. 准备数据2. 数据上传至HDFS3. 启动Spark Shell或创建Spark项目4. 读取数据5. 数据转换6. 创建临时视图…...

五线谱与简谱有什么区别 五线谱简谱混排怎么打 吉他谱软件哪个好

五线谱与简谱作为音乐记谱领域的两大主流系统&#xff0c;各自承载着深厚的历史渊源与独特的表现力&#xff0c;并在全球范围内被不同程度地接受和应用。尽管两者都是为了记录音乐作品中的音高和节奏信息&#xff0c;但其内在机制、适用范围以及学习曲线存在显著差别。下面我们…...

[C#][opencvsharp]C#使用opencvsharp进行年龄和性别预测支持视频图片检测

使用 OpenCVSharp 来调用 age_net.caffemodel 和 gender_net.caffemodel 来进行性别和年龄预测涉及几个步骤。以下是一个简化的流程和示例文案&#xff1a; 1. 准备工作 确保你已经安装了 OpenCVSharp 和相关的依赖项。确保你有 age_net.prototxt、age_net.caffemodel、gende…...

pdf拆分,pdf拆分在线使用,pdf拆分多个pdf

在数字化的时代&#xff0c;pdf文件已经成为我们日常办公、学习不可或缺的文档格式。然而&#xff0c;有时候我们可能需要对一个大的pdf文件进行拆分&#xff0c;以方便管理和分享。那么&#xff0c;如何将一个pdf文件拆分成多个pdf呢&#xff1f;本文将为你推荐一种好用的拆分…...

VScode Python debug:hydra.run.dir 写入launch.json

记录一个debug时的经验&#xff1a; VS code extension名称版本Pythonv2024.8.1Python Debuggerv2024.6.0 我配置的project运行 train.py 时需要在 terminal 输入参数 hydra.run.dirxxx 我想用 vscode debug 查看内部代码&#xff0c;按以往的经验需要将args写入launch.json&…...

windows USB设备驱动开发-双角色驱动

在USB的通讯协议中&#xff0c;规定发起连接的一方为主机(Host)&#xff0c;接受连接的一方为设备&#xff0c;这可以用U盘插入电脑举个例子&#xff0c;当U盘插入电脑后&#xff0c;电脑这边主动发起查询和枚举&#xff0c;U盘被动响应查询和数据存取。 USB 双角色驱动程序堆…...

二种方法轻松提取音频中的钢琴声音

在音乐制作、音频编辑或是纯粹的音乐爱好者的世界里&#xff0c;有时我们需要从复杂的音乐编排中抽取出特定乐器的声音&#xff0c;比如那悠扬的钢琴旋律。这不仅能帮助我们更好地理解音乐的结构&#xff0c;还能在创作过程中提供灵感。本文将介绍两种简单有效的方法&#xff0…...

OpenAI 推迟了 ChatGPT 的新语音模式

今年 5 月&#xff0c;OpenAI 首次为其人工智能聊天机器人平台ChatGPT演示了一种非常逼真、近乎实时的"高级语音模式"。几个月后&#xff0c;OpenAI 表示需要更多时间。 OpenAI 在其官方 Discord 服务器上发布了一篇文章&#xff0c;称其原计划于 6 月底开始向一小部…...

JVM原理(八):JVM虚拟机工具之基础故障工具

这里主要介绍监视虚拟机运行状态和进行故障处理的工具 1. jsp:虚拟机进程状况工具 jsp命令格式&#xff1a; jsp [options] [hostid] jps远程查询虚拟机进程状态 2. jstat:虚拟机统计信息监视工具 jstat命令格式&#xff1a; jstat [option vmid [interval [s|ms] [count]…...

前端:Tailwind CSS 与 Windicss

Tailwind CSS 和 windicss 都是基于 utility-first 的 CSS 框架&#xff0c;它们有着相似的设计原则和使用方法。但是它们也有一些不同之处&#xff1a; 文档和社区支持&#xff1a;Tailwind CSS 拥有强大的文档和社区支持&#xff0c;而 windicss 的文档和社区相对较小。这意味…...

彻底解决 macos中chrome应用程序 的 无法更新 Chrome 弹窗提示 mac自定义参数启动 chrome.app

mac系统中的chrome app应用在每次打开是都会提示一个 “无法更新 Chrome Chrome 无法更新至最新版本&#xff0c;因此您未能获得最新的功能和安全修复程序。” &#xff0c; 然而最新的chrome 程序似乎在某些情况下居然会出现 输入和显示不一致的情况&#xff0c;暂时不想升…...

现代或将为印度子公司IPO支付4,000万美元

盖世汽车讯 据路透社报道,有三位知情人士透露,为现代汽车印度首次公开募股提供咨询服务的银行将获得高达4,000万美元的佣金。这将是投资银行帮助企业在印度进行首次公开募股的第二高收费。现代汽车印度子公司本月已经向印度监管部门申请批准上市,这有望成为该国历史上最大的…...

vb.net,C#强制结束进程,“优雅”的退出方式

在VB.NET中&#xff0c;Application.Exit()和Environment.Exit(0)都用于结束程序&#xff0c;但它们的使用场景和背后的逻辑略有不同。 **Application.Exit()**&#xff1a; Application.Exit()通常用于Windows Forms应用程序中。当调用Application.Exit()时&#xff0c;它会触…...

内存函数<C语言>

前言 前面两篇文章介绍了字符串函数&#xff0c;不过它们都只能用来处理字符串&#xff0c;C语言中也内置了一些内存函数来对不同类型的数据进行处理&#xff0c;本文将介绍&#xff1a;memcpy()使用以及模拟实现&#xff0c;memmove()使用以及模拟实现&#xff0c;memset()使用…...

如何创建一个vue项目?详细教程,如何创建第一个vue项目?

已经安装node.js在自己找的到的地方新建一个文件夹用于存放项目&#xff0c;记住文件夹的存放路径&#xff0c;以我为例&#xff0c;我的文件夹路径为D:\tydic 打开cmd命令窗口&#xff0c;进入刚刚的新建文件夹 切换硬盘&#xff1a; D: 进入文件夹&#xff1a;cd tydic 使…...

Java中的死锁及其避免策略

一、技术难点&#xff1a; 在Java中&#xff0c;死锁是一个常见的并发问题&#xff0c;它指的是两个或更多的线程无限期地等待一个资源&#xff0c;而这些资源又被其他等待线程所持有。死锁通常发生在多个线程互相等待对方释放资源时&#xff0c;形成一个循环等待的条件。技术…...

CUDA学习备份

CUDA项目配置 1.项目属性->配置属性->常规->Windows SDK版本->选实际的版本 2.项目属性->CUDA C/C>Device->修改为对应CUDA型号的算力&#xff0c;例如算力3.5&#xff0c;就设置为compute_35 sm_35 概念&#xff1a; gpuAdd << <1, 1 >>…...