前言

记录下SpringBoot下静态资源存储服务器的搭建。

环境

win10 + SpringBoot2.5.3

实现效果

  • 文件上传:

  • 文件存储位置:

  • 文件访问:

具体实现

文件上传

配置类

  • pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
  • application.yml
spring:
# 文件编码 UTF8
mandatory-file-encoding: UTF-8 server:
# 服务端口
port: 8000 #文件上传配置
file:
# 文件服务域名
domain: http://localhost:8000/
# 排除文件类型
exclude:
# 包括文件类型
include:
- .jpg
- .png
- .jpeg
# 文件最大数量
nums: 10
# 服务器文件路径
serve-path: assets/**
# 单个文件最大体积
single-limit: 2MB
# 本地文件保存位置
store-dir: assets/
  • yml读取工厂类
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.List; /**
* @Description yml读取工厂类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public class YmlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
return sources.get(0);
}
}
  • 文件上传属性配置类
import com.coisini.file.factory.YmlPropertySourceFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component; /**
* @Description 文件上传属性配置类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Component
@ConfigurationProperties(prefix = "file")
@PropertySource(value = "classpath:application.yml",
encoding = "UTF-8",factory = YmlPropertySourceFactory.class)
public class FilePropertiesConfiguration { private static final String[] DEFAULT_EMPTY_ARRAY = new String[0]; private String storeDir = "/assets"; private String singleLimit = "2MB"; private Integer nums = 10; private String domain; private String[] exclude = DEFAULT_EMPTY_ARRAY; private String[] include = DEFAULT_EMPTY_ARRAY; public String getStoreDir() {
return storeDir;
} public void setStoreDir(String storeDir) {
this.storeDir = storeDir;
} public String getSingleLimit() {
return singleLimit;
} public void setSingleLimit(String singleLimit) {
this.singleLimit = singleLimit;
} public Integer getNums() {
return nums;
} public void setNums(Integer nums) {
this.nums = nums;
} public String[] getExclude() {
return exclude;
} public void setExclude(String[] exclude) {
this.exclude = exclude;
} public String[] getInclude() {
return include;
} public void setInclude(String[] include) {
this.include = include;
} public String getDomain() {
return domain;
} public void setDomain(String domain) {
this.domain = domain;
}
}

上传接口

  • 文件上传控制器
import com.coisini.file.vo.FileVo;
import com.coisini.file.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.List; /**
* @Description 文件上传控制器
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@RestController
@RequestMapping("/file")
public class FileController { @Autowired
private FileService fileService; /**
* 文件上传
* @param request
* @return
*/
@PostMapping("/upload")
public List<FileVo> upload(HttpServletRequest request) {
MultipartHttpServletRequest multipartHttpServletRequest = ((MultipartHttpServletRequest) request);
MultiValueMap<String, MultipartFile> fileMap = multipartHttpServletRequest.getMultiFileMap();
List<FileVo> files = fileService.upload(fileMap);
return files;
} }
  • 文件上传接口
import com.coisini.file.vo.FileVo;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; /**
* @Description 文件上传接口
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public interface FileService { /**
* 上传文件
* @param fileMap 文件map
* @return 文件数据
*/
List<FileVo> upload(MultiValueMap<String, MultipartFile> fileMap); }
  • 文件上传实现类
import com.coisini.file.model.FileModel;
import com.coisini.file.core.Uploader;
import com.coisini.file.vo.FileVo;
import com.coisini.file.service.FileService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.stream.Collectors; /**
* @Description 文件上传实现类
* @author coisini
* @date
* @Version 1.0
*/
@Service
public class FileServiceImpl implements FileService { @Autowired
private Uploader uploader; @Value("${file.domain}")
private String domain; @Value("${file.serve-path:assets/**}")
private String servePath; @Override
public List<FileVo> upload(MultiValueMap<String, MultipartFile> fileMap) {
return uploader.upload(fileMap).stream().map(item ->{
/**
* 这里可以拿到文件具体信息
* 在此做数据库保存记录操作等业务处理
*/
return transform(item);
}).collect(Collectors.toList());
} /**
* 出参序列化
* @param fileModel
* @return
*/
private FileVo transform(FileModel fileModel) {
FileVo model = new FileVo();
BeanUtils.copyProperties(fileModel, model);
String s = servePath.split("/")[0];
model.setUrl(domain + s + "/" + fileModel.getPath());
return model;
}
}

上传实现

  • 文件上传配置类
import com.coisini.file.core.LocalUploader;
import com.coisini.file.core.Uploader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; /**
* @Description 文件上传配置类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Configuration
public class UploaderConfiguration {
/**
* @return 本地文件上传实现类
*/
@Bean
@Order
@ConditionalOnMissingBean
public Uploader uploader(){
return new LocalUploader();
}
}
  • 文件上传服务接口
import com.coisini.file.model.FileModel;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; /**
* @Description 文件上传服务接口
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public interface Uploader { /**
* 上传文件
* @param fileMap 文件map
* @return 文件数据
*/
List<FileModel> upload(MultiValueMap<String, MultipartFile> fileMap); }
  • 本地上传实现类
import com.coisini.file.config.FilePropertiesConfiguration;
import com.coisini.file.model.FileModel;
import com.coisini.file.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List; /**
* @Description 本地上传
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Slf4j
public class LocalUploader implements Uploader { @Autowired
private FilePropertiesConfiguration filePropertiesConfiguration; /**
* 初始化本地存储
* 依赖注入完成后初始化
*/
@PostConstruct
public void initStoreDir() {
System.out.println("initStoreDir start:" + this.filePropertiesConfiguration.getStoreDir());
FileUtil.initStoreDir(this.filePropertiesConfiguration.getStoreDir());
System.out.println("initStoreDir end");
} /**
* 文件上传
* @param fileMap 文件map
* @return
*/
@Override
public List<FileModel> upload(MultiValueMap<String, MultipartFile> fileMap) {
// 检查文件
checkFileMap(fileMap);
return handleMultipartFiles(fileMap);
} /**
* 文件配置
* @return
*/
protected FilePropertiesConfiguration getFilePropertiesConfiguration() {
return filePropertiesConfiguration;
} /**
* 单个文件体积限制
* @return
*/
private long getSingleFileLimit() {
String singleLimit = getFilePropertiesConfiguration().getSingleLimit();
return FileUtil.parseSize(singleLimit);
} /**
* 检查文件
* @param fileMap
*/
protected void checkFileMap(MultiValueMap<String, MultipartFile> fileMap){
if (fileMap.isEmpty()) {
throw new RuntimeException("file not found");
} // 上传文件数量限制
int nums = getFilePropertiesConfiguration().getNums();
if (fileMap.size() > nums) {
throw new RuntimeException("too many files, amount of files must less than" + nums);
}
} /**
* 文件处理
* @param fileMap
* @return
*/
protected List<FileModel> handleMultipartFiles(MultiValueMap<String, MultipartFile> fileMap) {
long singleFileLimit = getSingleFileLimit();
List<FileModel> res = new ArrayList<>();
fileMap.keySet().forEach(key -> fileMap.get(key).forEach(file -> {
if (!file.isEmpty()) {
handleOneFile(res, singleFileLimit, file);
}
}));
return res;
} /**
* 单文件处理
* @param res
* @param singleFileLimit
* @param file
*/
private void handleOneFile(List<FileModel> res, long singleFileLimit, MultipartFile file) {
byte[] bytes = FileUtil.getFileBytes(file);
String[] include = getFilePropertiesConfiguration().getInclude();
String[] exclude = getFilePropertiesConfiguration().getExclude();
String ext = UploadHelper.checkOneFile(include, exclude, singleFileLimit, file.getOriginalFilename(), bytes.length);
String newFilename = UploadHelper.getNewFilename(ext);
String storePath = getStorePath(newFilename);
// 生成文件的md5值
String md5 = FileUtil.getFileMD5(bytes);
FileModel fileModelData = FileModel.builder().
name(newFilename).
md5(md5).
key(file.getName()).
path(storePath).
size(bytes.length).
extension(ext).
build(); boolean ok = writeFile(bytes, newFilename);
if (ok) {
res.add(fileModelData);
}
} /**
* 写入存储
* @param bytes
* @param newFilename
* @return
*/
protected boolean writeFile(byte[] bytes, String newFilename) {
// 获取绝对路径
String absolutePath =
FileUtil.getFileAbsolutePath(filePropertiesConfiguration.getStoreDir(), getStorePath(newFilename));
System.out.println("absolutePath:" + absolutePath);
try {
BufferedOutputStream stream =
new BufferedOutputStream(new FileOutputStream(new File(absolutePath)));
stream.write(bytes);
stream.close();
} catch (Exception e) {
System.out.println("write file error:" + e);
return false;
}
return true;
} /**
* 获取缓存地址
* @param newFilename
* @return
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
protected String getStorePath(String newFilename) {
Date now = new Date();
String format = new SimpleDateFormat("yyyy/MM/dd").format(now);
Path path = Paths.get(filePropertiesConfiguration.getStoreDir(), format).toAbsolutePath();
File file = new File(path.toString());
if (!file.exists()) {
file.mkdirs();
} return Paths.get(format, newFilename).toString();
}
}

辅助类

  • 文件上传Helper
import com.coisini.file.util.FileUtil;
import java.util.UUID; /**
* @Description 文件上传Helper
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public class UploadHelper { /**
* 单个文件检查
* @param singleFileLimit 单个文件大小限制
* @param originName 文件原始名称
* @param length 文件大小
* @return 文件的扩展名,例如: .jpg
*/
public static String checkOneFile(String[] include, String[] exclude, long singleFileLimit, String originName, int length) {
// 写到了本地
String ext = FileUtil.getFileExt(originName);
// 检测扩展
if (!UploadHelper.checkExt(include, exclude, ext)) {
throw new RuntimeException(ext + "文件类型不支持");
}
// 检测单个大小
if (length > singleFileLimit) {
throw new RuntimeException(originName + "文件不能超过" + singleFileLimit);
}
return ext;
} /**
* 检查文件后缀
* @param ext 后缀名
* @return 是否通过
*/
public static boolean checkExt(String[] include, String[] exclude, String ext) {
int inLen = include == null ? 0 : include.length;
int exLen = exclude == null ? 0 : exclude.length;
// 如果两者都有取 include,有一者则用一者
if (inLen > 0 && exLen > 0) {
return UploadHelper.findInInclude(include, ext);
} else if (inLen > 0) {
// 有include,无exclude
return UploadHelper.findInInclude(include, ext);
} else if (exLen > 0) {
// 有exclude,无include
return UploadHelper.findInExclude(exclude, ext);
} else {
// 二者都没有
return true;
}
} /**
* 检查允许的文件类型
* @param include
* @param ext
* @return
*/
public static boolean findInInclude(String[] include, String ext) {
for (String s : include) {
if (s.equals(ext)) {
return true;
}
}
return false;
} /**
* 检查不允许的文件类型
* @param exclude
* @param ext
* @return
*/
public static boolean findInExclude(String[] exclude, String ext) {
for (String s : exclude) {
if (s.equals(ext)) {
return true;
}
}
return false;
} /**
* 获得新文件的名称
* @param ext 文件后缀
* @return 新名称
*/
public static String getNewFilename(String ext) {
String uuid = UUID.randomUUID().toString().replace("-", "");
return uuid + ext;
}
}
  • 文件工具类
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path; /**
* @Description 文件工具类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public class FileUtil { /**
* 获取当前文件系统
* @return
*/
public static FileSystem getDefaultFileSystem() {
return FileSystems.getDefault();
} /**
* 是否绝对路径
* @param str
* @return
*/
public static boolean isAbsolute(String str) {
Path path = getDefaultFileSystem().getPath(str);
return path.isAbsolute();
} /**
* 初始化存储文件夹
* @param dir
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void initStoreDir(String dir) {
String absDir;
if (isAbsolute(dir)) {
absDir = dir;
} else {
String cmd = getCmd();
Path path = getDefaultFileSystem().getPath(cmd, dir);
absDir = path.toAbsolutePath().toString();
}
File file = new File(absDir);
if (!file.exists()) {
file.mkdirs();
}
} /**
* 获取程序当前路径
* @return
*/
public static String getCmd() {
return System.getProperty("user.dir");
} /**
* 获取文件绝对路径
* @param dir
* @param filename
* @return
*/
public static String getFileAbsolutePath(String dir, String filename) {
if (isAbsolute(dir)) {
return getDefaultFileSystem()
.getPath(dir, filename)
.toAbsolutePath().toString();
} else {
return getDefaultFileSystem()
.getPath(getCmd(), dir, filename)
.toAbsolutePath().toString();
}
} /**
* 获取文件扩展名
* @param filename
* @return
*/
public static String getFileExt(String filename) {
int index = filename.lastIndexOf('.');
return filename.substring(index);
} /**
* 获取文件MD5值
* @param bytes
* @return
*/
public static String getFileMD5(byte[] bytes) {
return DigestUtils.md5DigestAsHex(bytes);
} /**
* 文件体积
* @param size
* @return
*/
public static Long parseSize(String size) {
DataSize singleLimitData = DataSize.parse(size);
return singleLimitData.toBytes();
} /**
* 是否是绝对路径
* @param path
* @return
*/
public static boolean isAbsolutePath(String path) {
if (StringUtils.isEmpty(path)) {
return false;
} else {
return '/' == path.charAt(0) || path.matches("^[a-zA-Z]:[/\\\\].*");
}
} /**
* 文件字节
* @param file 文件
* @return 字节
*/
public static byte[] getFileBytes(MultipartFile file) {
byte[] bytes;
try {
bytes = file.getBytes();
} catch (Exception e) {
throw new RuntimeException("read file date failed");
}
return bytes;
}
}

实体

  • 文件具体信息
import lombok.*;

/**
* @Description 文件具体信息,可存储数据库
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FileModel { /**
* url
*/
private String url; /**
* key
*/
private String key; /**
* 文件路径
*/
private String path; /**
* 文件名称
*/
private String name; /**
* 扩展名,例:.jpg
*/
private String extension; /**
* 文件大小
*/
private Integer size; /**
* md5值,防止上传重复文件
*/
private String md5;
}
  • 文件出参
import lombok.Data;

/**
* @Description 文件出参
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Data
public class FileVo { /**
* 文件 key
*/
private String key; /**
* 文件路径
*/
private String path; /**
* 文件 URL
*/
private String url;
}

上传测试

  • 上传

  • 文件存储位置为当前项目/assets目录

文件访问

配置类

  • SpringBoot访问静态资源有两种方式:模板引擎和改变资源映射,这里采用改变资源映射来实现
  • Spring MVC配置类
import com.coisini.file.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.file.FileSystems;
import java.nio.file.Path; /**
* @Description Spring MVC 配置
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Configuration(proxyBeanMethods = false)
@Slf4j
public class WebConfiguration implements WebMvcConfigurer { @Value("${file.store-dir:assets/}")
private String dir; @Value("${file.serve-path:assets/**}")
private String servePath; /**
* 跨域设置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
} /**
* 拦截处理请求信息
* 添加文件真实地址
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(getDirServePath())
.addResourceLocations("file:" + getAbsDir() + "/");
} /**
* 获取服务器url
* @return
*/
private String getDirServePath() {
return servePath;
} /**
* 获得文件夹的绝对路径
*/
private String getAbsDir() {
if (FileUtil.isAbsolutePath(dir)) {
return dir;
}
String cmd = System.getProperty("user.dir");
Path path = FileSystems.getDefault().getPath(cmd, dir);
return path.toAbsolutePath().toString();
}
}
  • 访问结果

项目源码

Gitee: https://gitee.com/maggieq8324/java-learn-demo/tree/master/springboot-file-simple

- End -



梦想是咸鱼
关注一下吧

SpringBoot - 搭建静态资源存储服务器的更多相关文章

  1. 利用 MinIO 轻松搭建静态资源服务

    目录 1 引言 2 MinIO 简介 3 MinIO 运行与静态资源使用 3.1 MinIO 获取 3.2 MinIO 启动与运行 3.2.1 前台简单启动 3.2.2 后台指定参数运行 3.2.3 ...

  2. 使用Node.js搭建静态资源服务器

    对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解.那就开始吧,让我们的双手沾满网络请求! Note: 当然在项目中如 ...

  3. SpringBoot 配置静态资源映射

    SpringBoot 配置静态资源映射 (嵌入式servlet容器)先决知识 request.getSession().getServletContext().getRealPath("/& ...

  4. springboot设置静态资源不拦截的方法

    springboot设置静态资源不拦截的方法 springboot不拦截静态资源需配置如下的类: import org.springframework.context.annotation.Confi ...

  5. springboot下静态资源的处理(转)

    在SpringBoot中有默认的静态资源文件相关配置,需要通过如下源码跟踪: WebMvcAutoConfiguration-->configureResourceChain(method)-- ...

  6. IntelliJ IDEA+SpringBoot中静态资源访问路径陷阱:静态资源访问404

    IntelliJ IDEA+SpringBoot中静态资源访问路径陷阱:静态资源访问404 .embody{ padding:10px 10px 10px; margin:0 -20px; borde ...

  7. 使用 Nginx 搭建静态资源 web 服务器

    在搭建网站的时候,往往会加载很多的图片,如果都从 Tomcat 服务器来获取静态资源,这样会增加服务器的负载,使得服务器运行 速度非常慢,这时可以使用 Nginx 服务器来加载这些静态资源,这样就可以 ...

  8. springboot配置静态资源访问路径

    其实在springboot中静态资源的映射文件是在resources目录下的static文件夹,springboot推荐我们将静态资源放在static文件夹下,因为默认配置就是classpath:/s ...

  9. 【SpringBoot】06.SpringBoot访问静态资源

    SpringBoot访问静态资源 1.SpringBoot从classpath/static的目录 目录名称必须是static 启动项目,访问http://localhost:8080/0101.jp ...

随机推荐

  1. onclick="return doAlert()" onclick="return false"

    return false不是取bai消事件冒泡,而是取消"du浏览器默认行为".比如一个链接zhi<a href="http://zhidao.baidu.com& ...

  2. 利用swagger和API Version实现api版本控制

    场景: 在利用.net core进行api接口开发时,经常会因为需求,要开发实现统一功能的多版本的接口.比如版本V1是给之前用户使用,然后新用户有新需求,这时候可以单独给这个用户写接口,也可以在V1基 ...

  3. Js实现随机某个li样式增加

    一.首先引入jquery  cdn   二.基础样式 三.目的 为了使随机某个li背后有个旋转的图片 四.核心代码 html代码: <div class="bg3"> ...

  4. Quartz部署Linux的一个坑

    前言 最近做了一个项目,使用Quartz做定时任务,然后部署到了Linux服务器上,但是竟然很惊奇的跑不起来,已经在阿里云上的Linux上验证无数次了,后来经过不懈努力,终于发现了问题,我自己的Lin ...

  5. 四、C#简单操作MinIO

    MinIO的官方网站非常详细,以下只是本人学习过程的整理 一.MinIO的基本概念 二.Windows安装与简单使用MinIO 三.Linux部署MinIO分布式集群 四.C#简单操作MinIO He ...

  6. Cloud-init的安装和使用 --以ubuntu-server-14.04-amd64为例

    by hyc 1.Cloud-init安装 已有了一个安装好系统的镜像. 镜像名:ubuntu-test-14.04-server-amd64.img 用户名:user 密码:1 主机名:ubuntu ...

  7. rancherUI添加configmap

    1.创建configmap 2.部署pod,挂载配置文件(通过卷的形式引用)

  8. Build VM Cluster on CentOS Host

    Host Machine [root@bocoty49 ~]# lsb_release -a LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0 ...

  9. IDEA永久使用!!(很全)

    IDEA虽然好用,但是下载后只能试用30天,烦恼呀!所以今天就带来IDEA的激活版来帮助大家摆脱30天的苦恼! 准备工作: 破解所需要的软件和jar都在网盘里,需要的自行下载,在这里idea安装就不带 ...

  10. @ConfigurationProperties实现自定义配置绑定

    @ConfigurationProperties使用 创建一个类,类名上方注解,配置prefix属性,如下代码: @ConfigurationProperties( prefix = "he ...