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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/363920.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法金 | 决策树、随机森林、bagging、boosting、Adaboost、GBDT、XGBoost 算法大全

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 决策树是一种简单直观的机器学习算法&#xff0c;它广泛应用于分类和回归问题中。它的核心思想是将复杂的决策过程分解成一系列简单的决…

【PyTest】玩转HTML报告:修改、汉化和优化

前言 Pytest框架可以使用两种测试报告&#xff0c;其中一种就是使用pytest-html插件生成的测试报告&#xff0c;但是报告中有一些信息没有什么用途或者显示的不太好看&#xff0c;还有一些我们想要在报告中展示的信息却没有&#xff0c;最近又有人问我pytest-html生成的报告&a…

技术驱动的音乐变革:AI带来的产业重塑

&#x1f4d1;引言 近一个月来&#xff0c;随着几款音乐大模型的轮番上线&#xff0c;AI在音乐产业的角色迅速扩大。这些模型不仅将音乐创作的门槛降至前所未有的低点&#xff0c;还引发了一场关于AI是否会彻底颠覆音乐行业的激烈讨论。从初期的兴奋到现在的理性审视&#xff0…

【算能全国产AI盒子】基于BM1688CV186AH+FPGA智能物联工作站,支持差异化泛AI视觉产品定制

在数据呈现指数级增长的今天&#xff0c;越来越多的领域和细分场景对实时、高效的数据处理和分析的需求日益增长&#xff0c;对智能算力的需求也不断增强。为应对新的市场趋势&#xff0c;凭借自身的硬件研发优势&#xff0c;携手算能相继推出了基于BM1684的边缘计算盒子&#…

IDM(Internet Download Manager)下载器的安装激活与换机方法 IDM怎么用

很多人都知道 Internet Download Manager(以下简称 IDM)是一款非常优秀的下载提速软件。它功能强大&#xff0c;几乎能下载网页中的所有数据&#xff08;包括视频、音频、图片等&#xff09;&#xff0c;且适用于现在市面上几乎所有的浏览器&#xff0c;非常受大家欢迎。IDM 是…

React 扩展

文章目录 PureComponent1. 使用 React.Component&#xff0c;不会进行浅比较2. 使用 shouldComponentUpdate 生命周期钩子&#xff0c;手动比较3. 使用 React.PureComponent&#xff0c;自动进行浅比较 Render Props1. 使用 Children props&#xff08;通过组件标签体传入结构&…

java虚拟机栈帧操作

虚拟机栈(Virtual Machine Stack)是虚拟机(如JVM、Python VM等)用来管理方法调用和执行的栈结构。它主要用于存储方法调用的相关信息,包括局部变量、操作数栈、动态链接和方法返回地址等。 java虚拟机栈操作的基本元素就是栈帧,栈帧主要包含了局部变量表、操作数栈、动态…

鸿蒙 HarmonyOS NEXT星河版APP应用开发-阶段一

一、鸿蒙开发环境搭建 DevEco Studio安装 下载 访问官网&#xff1a;https://developer.huawei.com/consumer/cn/deveco-studio/选择操作系统版本后并注册登录华为账号既可下载安装包 安装 建议&#xff1a;软件和依赖安装目录不要使用中文字符软件安装包下载完成后&#xff0…

ubuntu丢失网络/网卡的一种原因解决方案

现象 开机进入ubuntu后发现没有网络&#xff0c;无论是在桌面顶部状态栏的快捷键 还是 系统设置中&#xff0c;都没有”有线网“和”无线网“的选项&#xff0c;”代理“的选项是有的使用数据线连接电脑和手机&#xff0c;手机开启”通过usb共享网络“&#xff0c;还是没有任何…

小程序web-view无法打开该页面的解决方法

问题&#xff1a;开发者工具可以正常打开&#xff0c;正式上线版小程序使用 web-view 组件测试时提示&#xff1a;“无法打开该页面&#xff0c;不支持打开 https://xxxxxx&#xff0c;请在“小程序右上角更多->反馈与投诉”中和开发者反馈。” 解决方法&#xff1a;需要配…

ctfshow web 其他 web432--web449

web432 过滤了os|open|system|read|eval ?codestr(.__class__.__bases__[0].__subclasses__[185].__init__.__globals__[__builtins__][__import__](os).__dict__[popen](curl http://ip:port?1cat /f*)) ?codestr(.__class__.__bases__[0].__subclasses__()[185].__init_…

Qt开发笔记:Qt3D三维开发笔记(一):Qt3D三维开发基础概念介绍

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/140059315 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、O…

HarmonyOS--路由管理--组件导航 (Navigation)

文档中心 什么是组件导航 (Navigation) &#xff1f; 1、Navigation是路由容器组件&#xff0c;一般作为首页的根容器&#xff0c;包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式 2、Navigation组件适用于模块内和跨模块的路由切换&#xff0c;一次开发&#xff0…

show-overflow-tooltip 解决elementui el-table标签自动换行的问题

elementui中 el-table中某一行的高度不想因为宽度不够而撑开换行展示的解决方法。可通过show-overflow-tooltip属性解决&#xff0c;如下 代码是这样的 <el-table-column width"80" prop"id" label"ID"></el-table-column> <el…

npm创建一个空的vue3项目的方法或者pnpm创建vue3项目

1、前提我们已经安装了npm&#xff0c;或者pnpm 2、我们用npm来创建vue3项目 快速上手 | Vue.js 官网地址 这里我安装是的 node v18.20.3 以下是安装过程 &#xff1a; npm create vuelatest 根据自己的需要进行创建即可。 3、我们用pnpm来创建vite vue3项目 pnpm create …

用通俗易懂方式讲解:快速部署大模型 ChatGLM3 并进行推理

在深入了解了一些大模型的知识之后&#xff0c;最好的方法是亲自动手搭建一个开源的大模型&#xff0c;以更深入地理解其工作原理。 在此基础上&#xff0c;我们将以 ChatGLM3 为例进行部署及推理&#xff0c;从而进一步探索大模型的应用和实践。 ChatGLM3简介&#xff1a; …

Ubuntu20.04安装LibTorch并完成高斯溅射环境搭建

0. 简介 最近受到优刻得的使用邀请&#xff0c;正好解决了我在大模型和自动驾驶行业对GPU的使用需求。UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&#xff0c;并附带200G的免费…

AI数据分析003:用kimi生成一个正弦波数学动画

文章目录 一、正弦波公式二、输入内容三、输出内容一、正弦波公式 ƒ(x) = a * sin(x + x0) + b 公式中: a: 决定正弦函数振动幅度的大小; x0:表示x开始比0拖后的弧度值; b:表示函数偏离X轴的距离; 对于难以理解的学生来说,可以用动画把这个公式直观的展现出来。 二…

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;并在四次握手的第三步中…

一文掌握 Object 类里的所有方法(wait、notify、finalize)

Object 概述 Object 类是 Java 中所有类的父类&#xff0c;这个类中包含了若干方法&#xff0c;这也就意味着所有类都将继承这些方法。因此&#xff0c;掌握这个类的方法是非常必要的&#xff0c;毕竟所有类都能为你提供这些方法。 Object 类位于 java.base 模块下 java.lang…