我是靠谱客的博主 会撒娇寒风,最近开发中收集的这篇文章主要介绍SpringBoot2.7.8整合Minio文件上传下载案例1. pom.xml2. application.yml3. MinioCilent配置类4. MinioUtil工具类5. Controller上传案例6. index.html(前端上传代码),觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
SpringBoot2.7.8整合Minio文件上传下载案例
- 1. pom.xml
- 2. application.yml
- 3. MinioCilent配置类
- 4. MinioUtil工具类
- 5. Controller上传案例
- 6. index.html(前端上传代码)
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yuan</groupId>
<artifactId>yuan-boot-minio</artifactId>
<version>1.0.0</version>
<name>yuan-boot-minio</name>
<description>yuan-boot-minio</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. application.yml
server:
port: 8810
spring:
servlet:
multipart:
# 单文件大小限制
max-file-size: 100MB
# 上传总大小限制
max-request-size: 500MB
# minio配置
minio:
endpoint: http://localhost:9000
# minio客户端口 Access Keys模块中创建的 accessKey与secretKey
accessKey: Da2FVnOqZ41k15Ha
secretKey: TGN6lY7ZB2Ft3b5u7i94zlCiRpt357dO
#bucket
bucketName: yuantest
3. MinioCilent配置类
package com.yuan.config;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description: minio配置
* @Author: 袁金生
* @CreateTime: 2023/2/3 15:21
* @LastEditors: 袁金生
* @LastEditTime: 2023/2/3 15:21
*/
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Bean
public MinioClient minioClient() {
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
}
}
4. MinioUtil工具类
package com.yuan.util;
import io.minio.*;
import io.minio.errors.*;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* @Description: Minio操作工具类
* @Author: 袁金生
* @CreateTime: 2023/2/3 15:36
* @LastEditors: 袁金生
* @LastEditTime: 2023/2/3 15:36
*/
public class MinioUtil implements Serializable {
/**
* 判断bucket是否存在,不存在则创建
*
* @param minioClient
* @param bucketName
* @throws Exception
*/
public static final void bucketExists(MinioClient minioClient, String bucketName) throws Exception {
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!found) {
// Make a new bucket called 'bucketName'.
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} else {
System.out.println("Bucket '" + bucketName + "' already exists.");
}
}
/**
* 上传对象
*
* @param minioClient 客户端操作对象
* @param bucketName
桶名称
* @param objectName
文件名
* @param filePath
文件路径
* @return
* @throws Exception
* @throws ServerException
* @throws InsufficientDataException
* @throws ErrorResponseException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws InvalidResponseException
* @throws XmlParserException
* @throws InternalException
*/
public static final ObjectWriteResponse uploadObject(MinioClient minioClient, String bucketName, String objectName, String filePath) throws Exception {
bucketExists(minioClient, bucketName);
return minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(filePath).build());
}
/**
* @param minioClient 客户端操作对象
* @param bucketName
桶名称
* @param objectName
文件名
* @param contentType 文件类型
* @param filePath
文件路径
* @return
* @throws Exception
*/
public static final ObjectWriteResponse uploadObject(MinioClient minioClient, String bucketName, String objectName, String contentType, String filePath) throws Exception {
bucketExists(minioClient, bucketName);
return minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(filePath).contentType(contentType).build());
}
/**
* 上传MultipartFile接收的文件
*
* @param minioClient minio客户端对象
* @param bucketName
桶名
* @param objectName
文件名,如 aa.png
* @param file
MultipartFile对象
* @return
* @throws Exception
*/
public static final ObjectWriteResponse uploadObjectByMultipartFile(MinioClient minioClient, String bucketName, String objectName, MultipartFile file) throws Exception {
bucketExists(minioClient, bucketName);
/* InputStream inputStream = file.getInputStream();
return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(file.getContentType()).stream(inputStream, inputStream.available(), -1).build());*/
return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(file.getContentType()).stream(file.getInputStream(), file.getSize(), -1).build());
}
/**
* 对上传的文件名进行处理:最终格式为:文件名_当前系统时间的毫米数.后缀类型
*
* @param fileName
* @return
*/
public static final String replaceFileName(String fileName) {
String newFileName = null;
if (StringUtils.hasLength(fileName)) {
String fileNamePre = fileName.substring(0, fileName.lastIndexOf("."));
String fileNameSuffix = fileName.substring(fileName.lastIndexOf("."));
newFileName = fileNamePre + "_" + System.currentTimeMillis() + fileNameSuffix;
}
return newFileName;
}
/**
* 对上传的文件名进行处理:文件名 指定的分隔符 当前系统时间的毫秒数.后缀类型
*
* @param fileName
* @param splitStr
* @return
*/
public static final String replaceFileName(String fileName, String splitStr) {
String newFileName = null;
if (StringUtils.hasLength(fileName)) {
String fileNamePre = fileName.substring(0, fileName.lastIndexOf("."));
String fileNameSuffix = fileName.substring(fileName.lastIndexOf("."));
newFileName = fileNamePre + (splitStr == null ? "_" : splitStr) + System.currentTimeMillis() + fileNameSuffix;
}
return newFileName;
}
/**
* description: 从minio下载对象到本地_通过流的方式输出
*
* @param minioClient
MinioClient客户端对象
* @param bucketName
桶名
* @param objectName
对象全名,如: yuantest/test/3639dfb5ceeb4ae290a3de68c8321cac.docx
* @param fileOutputPath fileOutputPath 文件输出位置: 如:D:aafilesJava开发手册(黄山版).pdf
* @throws Exception
*/
public static void downloadToLocalByStream(MinioClient minioClient, String bucketName, String objectName, String fileOutputPath) throws Exception {
InputStream in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
FileOutputStream outputStream = new FileOutputStream(fileOutputPath);
byte[] buf = new byte[2048];
int len;
while ((len = in.read(buf, 0, buf.length)) >= 0) {
outputStream.write(buf, 0, len);
}
outputStream.close();
in.close();
}
/**
* description: 从minio下载对象到本地_通过minio直接输出
*
* @param minioClient
MinioClient客户端对象
* @param bucketName
桶名
* @param objectName
对象全名,如: yuantest/test/3639dfb5ceeb4ae290a3de68c8321cac.docx
* @param fileOutputPath 文件输出位置: 如:D:aafilesJava开发手册(黄山版).pdf
* @throws Exception
*/
public static void downloadToLocalByMinio(MinioClient minioClient, String bucketName, String objectName, String fileOutputPath) throws Exception {
minioClient.downloadObject(DownloadObjectArgs
.builder()
.bucket(bucketName)
//objectName
.object(objectName)
//outputpath,直接指定文件的输出位置+文件名即可
.filename(fileOutputPath)
.build());
}
}
5. Controller上传案例
package com.yuan.controller;
import com.yuan.util.ApiResult;
import com.yuan.util.MinioUtil;
import io.minio.*;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;
/**
* @Description: 文件上传Controller
* @Author: 袁金生
* @CreateTime: 2023/2/3 17:35
* @LastEditors: 袁金生
* @LastEditTime: 2023/2/3 17:35
*/
@Slf4j
@RestController
@RequestMapping("/upload")
public class MinioUploadController {
@Value("${minio.bucketName}")
public String defaultBucketName;
@Resource
private MinioClient minioClient;
@Autowired
private RestTemplate restTemplate;
@PostMapping("/uploadOne")
public ApiResult uploadOne(MultipartFile file, @RequestParam(name = "bucketName", required = false) String bucketName) {
if (file != null) {
if (!StringUtils.hasLength(bucketName)) {
bucketName = defaultBucketName;
}
try {
MinioUtil.uploadObjectByMultipartFile(minioClient, bucketName, MinioUtil.replaceFileName(file.getOriginalFilename()), file);
return ApiResult.success();
} catch (Exception e) {
e.printStackTrace();
return ApiResult.error("上传失败,错误信息:" + e.getMessage());
}
} else {
return ApiResult.error("上传文件不能为空");
}
}
/**
* 多文件上传
*
* @param files
* @param bucketName
* @return
*/
@PostMapping("/uploadMultipartFile")
public ApiResult uploadMultipartFile(MultipartFile[] files, @RequestParam(name = "bucketName", required = false) String bucketName) {
if (files.length > 0) {
if (!StringUtils.hasLength(bucketName)) {
bucketName = defaultBucketName;
}
//上传错误信息存储List
List<Map<String, String>> errorList = new ArrayList<>();
String finalBucketName = bucketName;
Arrays.stream(files).forEach(file -> {
try {
MinioUtil.uploadObjectByMultipartFile(minioClient, finalBucketName, MinioUtil.replaceFileName(file.getOriginalFilename()), file);
} catch (Exception e) {
//return ApiResult.error("上传失败,错误信息:" + e.getMessage());
Map<String, String> errorMap = new HashMap<>();
errorMap.put(file.getOriginalFilename() + "上传失败", "异常信息:" + e.getMessage());
errorList.add(errorMap);
//throw new RuntimeException(e);
}
});
//错误信息errorList对象为空则表示所有文件上传成功,否则返回错误信息
if (errorList.isEmpty()) {
return ApiResult.success("所有文件以上传成功");
} else {
return ApiResult.error("上传失败", errorList);
}
} else {
return ApiResult.error("上传文件不能为空");
}
}
/**
* 文件下载
*
* @param bucketName
* @param fileName
* @param response
*/
@GetMapping("/downloadByStream")
public void downloadByStream(@RequestParam(name = "bucketName", required = false) String bucketName, @RequestParam("fileName") String fileName, HttpServletResponse response) {
InputStream inputStream = null;
try {
if (!StringUtils.hasLength(bucketName)) {
bucketName = defaultBucketName;
}
// 获取对象的元数据
String contentType = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build()).contentType();
response.setContentType(contentType);
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
IOUtils.copy(inputStream, response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
log.error("文件下载失败", e.getMessage());
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 删除文件
*
* @param bucketName
* @param fileName
* @param response
*/
@GetMapping("/deleteFile")
public ApiResult deleteObject(@RequestParam(name = "bucketName", required = false) String bucketName, @RequestParam("fileName") String fileName, HttpServletResponse response) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
return ApiResult.success("删除成功");
} catch (Exception e) {
e.printStackTrace();
return ApiResult.error("删除失败", "错误信息:" + e.getMessage());
}
}
/**
* 获取当前桶下的所有文件信息
*
* @return
*/
@GetMapping("/getObjectByBucketName")
public ApiResult getObjectByBucketName(@RequestParam("bucketName") String bucketName) {
try {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
return ApiResult.success(results);
} catch (Exception e) {
e.printStackTrace();
return ApiResult.error("获取所有桶信息失败");
}
}
/**
* 获取所有桶信息
*
* @return
*/
@GetMapping("/getAllBuckets")
public ApiResult getAllBuckets() {
try {
List<Bucket> buckets = minioClient.listBuckets();
return ApiResult.success(buckets);
} catch (Exception e) {
e.printStackTrace();
return ApiResult.error("获取所有桶信息失败");
}
}
/**
* 调用远程接口上传文件
*
* @return
*/
@GetMapping("/remoteUploadOne")
public ApiResult remoteUploadOne() {
try {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("bucketName", "yuantest");
parts.add("file", new FileSystemResource("D:\aa\files\aaatest.pdf"));
ApiResult apiResult = restTemplate.postForObject("http://localhost:7002/std/upload/uploadOne", parts, ApiResult.class);
System.out.println(apiResult.toString());
return ApiResult.success(apiResult.getResult());
} catch (Exception e) {
e.printStackTrace();
return ApiResult.error("远程上传成功");
}
}
/**
* 调用远程接口上传文件_设置请求头方式
*
* @return
*/
@GetMapping("/remoteUploadOneAndSetHeaders")
public ApiResult remoteUploadOneAndSetHeaders() {
try {
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("bucketName", "yuantest");
multiValueMap.add("file", new FileSystemResource("D:\aa\files\aaatest.pdf"));
//设置请求头信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(multiValueMap, headers);
ApiResult apiResult = restTemplate.postForObject("http://localhost:7002/std/upload/uploadOne", httpEntity, ApiResult.class);
System.out.println(apiResult.toString());
return ApiResult.success(apiResult.getResult());
} catch (Exception e) {
e.printStackTrace();
return ApiResult.error("远程上传成功");
}
}
/**
* 单文件上传简单测试,以 aa.png 为例对下面参数进行说明:
* file.getOriginalFilename(): aa.png
* file.getName(): file
* file.getContentType(): image/png
* file.getSize(): 27890
*
* @param
* @return
*/
@RequestMapping("/upload")
public ApiResult upload(MultipartFile file) {
if (file != null) {
System.out.println(file.getOriginalFilename());
String fileName = file.getOriginalFilename();
String[] split = fileName.split("\.");
System.out.println("分割:" + split.toString());
String fileNamePre = fileName.substring(0, fileName.lastIndexOf("."));
String fileNameSuffix = fileName.substring(fileName.lastIndexOf("."));
System.out.println("fileNamePre:" + fileNamePre);
System.out.println("fileNameSuffix:" + fileNameSuffix);
System.out.println(file.getName());
System.out.println(file.getContentType());
System.out.println(file.getSize());
try {
file.transferTo(new File("D:\aa\imageConvert\" + file.getOriginalFilename()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return ApiResult.success();
}
}
6. index.html(前端上传代码)
使用thymeleaf模板与后端接口交互
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.88.1">
<title>mino文件上传下载测试</title>
</head>
<body id="app">
<!--
文件上传form表单提交方法为 :method="post"
enctype: 指定类型为文件上传类型,如 enctype="multipart/form-data",默认:enctype="application/x-www-form-urlencoded"
enctype: 取值类型有三:application/x-www-form-urlencoded(默认)、multipart/form-data(文件上传)、text/plain(文本)
-->
<h3>单文件上传</h3><br>
<form id="uploadOne" th:action="@{/upload/uploadOne}" method="post" enctype="multipart/form-data">
文件:<input type="file" name="file" placeholder="请选择文件"/>
<div style="margin-top: 5px"><input type="submit" value="上传"></div>
</form>
<hr>
<h3>多件上传</h3><br>
<form id="uploadMulti" th:action="@{/upload/uploadMultiple}" method="post" enctype="multipart/form-data">
桶:<input type="text" name="bucketName" value="labtest"/><br>
文件:<input type="file" name="files" multiple="multiple" placeholder="请选择文件"/>
<div style="margin-top: 5px"><input type="submit" value="上传"></div>
</form>
<hr>
<!--
multiple:可选择多文件上传,不设置默认只能选择文件
accept: 限制可选择上传的文件类型
-->
<h3>多件上传_限定上传的类型</h3><br>
<form id="uploadMultiple" th:action="@{/upload/uploadMultiple}" method="post" enctype="multipart/form-data">
桶:<input type="text" name="bucketName" value="labtest"/><br>
文件:<input type="file" name="files" multiple="multiple" accept="application/pdf,image/png" placeholder="请选择文件"/>
<div style="margin-top: 5px"><input type="submit" value="上传"></div>
</form>
<hr>
<h3>文件下载</h3><br>
<form id="download" th:action="@{/upload/downloadByStream}" method="get">
桶:<input type="text" name="bucketName" value="labtest"/><br>
文件名:<input type="text" name="fileName" placeholder="请收入文件名"/>
<div style="margin-top: 5px"><input type="submit" value="下载"></div>
</form>
<hr>
<h3>文件删除</h3><br>
<form id="delete" th:action="@{/upload/deleteFile}" method="get">
桶:<input type="text" name="bucketName" value="labtest"/><br>
文件名:<input type="text" name="fileName" placeholder="请收入文件名"/>
<div style="margin-top: 5px"><input type="submit" value="删除"></div>
</form>
<hr>
<h3>调用远程接口上传</h3><br>
<form id="remoteUploadOne" th:action="@{/upload/remoteUploadOne}" method="get">
<div style="margin-top: 5px"><input type="submit" value="远程调用"></div>
</form>
<hr>
<h3>调用远程接口上传_设置请求头方式</h3><br>
<form id="remoteUploadOneSetHeaders" th:action="@{/upload/remoteUploadOneAndSetHeaders}" method="get">
<div style="margin-top: 5px"><input type="submit" value="远程调用1"></div>
</form>
<hr>
<!--
accept:表示可以选择的文件类型,多个类型用英文逗号分开,常用的类型见下表。
*.3gpp
audio/3gpp, video/3gpp
*.ac3
audio/ac3
*.asf
allpication/vnd.ms-asf
*.au
audio/basic
*.css
text/css
*.csv
text/csv
*.doc
application/msword
*.dot
application/msword
*.dtd
application/xml-dtd
*.dwg
image/vnd.dwg
*.dxf
image/vnd.dxf
*.gif
image/gif
*.htm
text/html
*.html
text/html
*.jp2
image/jp2
*.jpe
image/jpeg
*.jpeg
image/jpeg
*.jpg
image/jpeg
*.js
text/javascript, application/javascript
*.json
application/json
*.mp2
audio/mpeg, video/mpeg
*.mp3
audio/mpeg
*.mp4
audio/mp4, video/mp4
*.mpeg
video/mpeg
*.mpg
video/mpeg
*.mpp
application/vnd.ms-project
*.ogg
application/ogg, audio/ogg
*.pdf
application/pdf
*.png
image/png
*.pot
application/vnd.ms-powerpoint
*.pps
application/vnd.ms-powerpoint
*.ppt
application/vnd.ms-powerpoint
*.rtf
application/rtf, text/rtf
*.svf
image/vnd.svf
*.tif
image/tiff
*.tiff
image/tiff
*.txt
text/plain
*.wdb
application/vnd.ms-works
*.wps
application/vnd.ms-works
*.xhtml
application/xhtml+xml
*.xlc
application/vnd.ms-excel
*.xlm
application/vnd.ms-excel
*.xls
application/vnd.ms-excel
*.xlt
application/vnd.ms-excel
*.xlw
application/vnd.ms-excel
*.xml
text/xml, application/xml
*.zip
application/zip
*.xlsx
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
-->
</body>
</html>
最后
以上就是会撒娇寒风为你收集整理的SpringBoot2.7.8整合Minio文件上传下载案例1. pom.xml2. application.yml3. MinioCilent配置类4. MinioUtil工具类5. Controller上传案例6. index.html(前端上传代码)的全部内容,希望文章能够帮你解决SpringBoot2.7.8整合Minio文件上传下载案例1. pom.xml2. application.yml3. MinioCilent配置类4. MinioUtil工具类5. Controller上传案例6. index.html(前端上传代码)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复