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.mzph.cn/bicheng/37386.shtml

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

相关文章

Arduino - TM1637 4 位 7 段显示器

Arduino - TM1637 4 位 7 段显示器 Arduino-TM1637 4 位 7 段显示器 A standard 4-digit 7-segment display is needed for clock, timer and counter projects, but it usually requires 12 connections. The TM1637 module makes it easier by only requiring 4 connectio…

有哪些防爬虫的方法

防爬虫的方法有robots.txt文、user-agent过滤、ip限制、验证码、动态页面生成、频率限制、动态url参数和反爬虫技术等。详细介绍&#xff1a;1、robots.txt文件&#xff0c;用于告诉搜索引擎爬虫哪些页面可以访问&#xff0c;哪些页面禁止访问&#xff1b;2、ip限制&#xff0c…

关于vs code中Live Server插件安装后无法打开的问题

一、问题情况 安装好Live Server插件之后&#xff0c;点击open with live server只会出现界面右下角落的提示&#xff0c;但是不会跳转到浏览器的页面&#xff1a;如下所示&#xff1a; 二&#xff1a;解决步骤 1、首先进行扩展设置&#xff0c;默认将浏览器的设置为chrome浏览…

vue组件全局注册

描述&#xff1a; vue组件的注册分为局部和全局注册两部分&#xff0c;局部注册相对容易&#xff0c;不做赘述&#xff1b;而不同框架的注册方法又有所不同&#xff0c;下面针对vite框架和vue-cli框架的注册分别进行说明 vue组件全局注册 一、vite框架中全局组件注册二、Vue-cl…

-bash: /snap/bin/docker: 没有那个文件或目录

-bash: /snap/bin/docker: 没有那个文件或目录 解决办法 export PATH$PATH:/usr/bin/docker然后&#xff0c;重新加载配置文件 source ~/.bashrc

线程的等待通知机制

等待通知机制 之前所学到的join是等待线程结束,而此时的等待通知,等待代码给我们提示进行显示的通知(并不一定要结束),可以更加精细控制线程之间的执行顺序,在系统内部,线程是抢占式执行,随机调度,但是程序员也是有手段可以进行干预的,我们可以通过"等待"的方式让线…

【学术日记】关于读博,目标院校,意向导师,毕业要求,重要时间点

文章目录 一、目标院校二、重要时间点西安交通大学意向导师 华南理工大学意向导师 本文记录博主的科研日记。如果对博主的其他文章感兴趣&#xff0c;可以看这篇文章【CSDN文章】晚安66博客文章索引。 首次修改时间&#xff1a;2024年5月12日。当前修改时间&#xff1a;2024年5…

C : 线性规划例题求解

Submit Page TestData Time Limit: 1 Sec Memory Limit: 128 Mb Submitted: 93 Solved: 49 Description 求解下述线性规划模型的最优值min &#xfffd;1&#xfffd;1&#xfffd;2&#xfffd;2&#xfffd;3&#xfffd;3&#xfffd;.&#xfffd;. &…

Spring Cloud LoadBalancer基础入门与应用实践

官网地址&#xff1a;https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/loadbalancer.html 【1】概述 Spring Cloud LoadBalancer是由SpringCloud官方提供的一个开源的、简单易用的客户端负载均衡器&#xff0c;它包含在SpringCloud-commons中用…

前端vue使用onlyoffice控件实现word在线编辑、预览(仅列出前端部分需要做的工作,不包含后端部分)

简介 ONLYOFFICE 文档 是一个开源办公套件&#xff0c;包括文本文档、电子表格、演示文稿和可填写表单的编辑器。 它提供以下功能&#xff1a; 创建、编辑和查看文本文档、电子表格、演示文稿和可填写表单&#xff1b; 与其他队友实时协作处理文件。 基于这个控件&#xff0c;…

基于Java毕业生生活用品出售网站的设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

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

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

MySQL3(多表联查 子查询 流程控制函数,语句 Sql 执行顺序 学生选课表练习)

目录 一、多表联查 1. 等值查询和非等值查询 等值查询 --- 主外键相等 ​编辑非等值查询 2. 连接查询 ​编辑 内连接 ​编辑 左外连接 ​编辑 右外连接 3. UNION 二、子查询 1. where 型子查询&#xff1a; 2. from型子查询: 3. exists型子查询: 4. any, some…

笔记本重装系统怎么操作? windows电脑重装系统,超实用的四种方法

重新安装操作系统是维护计算机性能和确保系统稳定运行的重要步骤。对于 Windows 笔记本用户而言&#xff0c;熟悉重装系统的方法可以帮助他们解决各种问题&#xff0c;从提高系统速度到修复软件故障。然而具体来讲&#xff0c;笔记本重装系统怎么操作呢&#xff1f;接下来&…

LLDP 基本原理

LLDP 简介 定义 LLDP&#xff08;Link Layer Discovery Protocol&#xff0c;链路层发现协议&#xff09;是 IEEE 802.1ab 中定义的第二层发现&#xff08;Layer 2 Discovery&#xff09;协议。 LLDP 提供了一种标准的链路层发现方式&#xff0c;可以将本端设备的主要能力、…

单片机使用printf在串口输出字符串

把字符串使用printf输出的本质 实际上调用了putchar和串口字符输出函数&#xff0c;参考 以51单片机中的程序为例 在主函数中使用printf函数向串口发送字符串&#xff0c;当然保证已经定义好串口的波特率等参数 while(1){//uart0SendString("start....\n");prin…

服务器巡查脚本

脚本编程步骤 脚本编程一般分为以下几个步骤&#xff1a; 需求分析&#xff1a;根据系统管理的需求&#xff0c;分析脚本要实现的功能、功能实现的层次、实现的命令与语句等&#xff1b; 命令测试&#xff0c;将要用到的命令逐个进行测试&#xff0c;以决定使用的选项要设置…

新书速览|解密AI绘画与修图: Stable Diffusion+Photoshop

《解密AI绘画与修图&#xff1a; Stable DiffusionPhotoshop》 本书内容 《解密AI绘画与修图&#xff1a;Stable DiffusionPhotoshop》全面介绍了Photoshop和Stable Diffusion的交互方式&#xff0c;以及各自的AI功能和具体使用方法。除了讲解功能&#xff0c;还通过实际案例加…

SpringBoot防抖方案(防止表单重复提交)

SpringBoot防抖方案&#xff08;防止表单重复提交&#xff09; 1.应用场景&#xff08;什么是防抖&#xff09; 所谓防抖&#xff0c;一是防用户手抖&#xff0c;二是防网络抖动。在Web系统中&#xff0c;表单提交是一个非常常见的功能&#xff0c;如果不加控制&#xff0c;容…

深度遍历-牛牛的果实迷宫

目录 一、问题描述 二、解题思路 1.返回格式 2.使用深度遍历 3.注意上下左右的实现方式 三、代码实现 四、刷题链接 一、问题描述 二、解题思路 1.返回格式 这个题目的问题返回格式是Point(x,y)&#xff1b;x代表最短路径距离&#xff0c;y表示最短路径数量 如果没有…