0%

/images/minio-logo.svg

在Docker中运行Minio

minio需要一个稳定的运行环境,测试的时候我们可以再docker中运行一个简单的实例,📢注意这个例子并没有映射任何volume,所以停止container的时候数据也会销毁

1
2
3
4
5
6
docker run \
-p 9000:9000 \
-p 9001:9001 \
-e "MINIO_ROOT_USER=<USERNAME>" \
-e "MINIO_ROOT_PASSWORD=<PASSWORD>" \
quay.io/minio/minio server /data --console-address ":9001"

集成到Spring boot中

在Spring boot中的pom.xml增加依赖

这里用的maven

1
2
3
4
5
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.0</version>
</dependency>

在application.properties中增加相关配置项,相关配置修改为自己的

1
2
3
4
5
6
7
8
server.port=8080
spring.servlet.multipart.max-file-size=2MB

# Minio
minio.bucket.name=minio-example-demo
minio.access.key=minioadmin
minio.access.secret=minioadmin
minio.url=https://play.min.io

配置Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class MinioConfiguration {

@Value("${minio.access.key}")
private String accessKey;

@Value("${minio.access.secret}")
private String secretKey;

@Value("${minio.url}")
private String minioUrl;

@Bean
@Primary
public MinioClient minioClient() {
return new MinioClient.Builder()
.credentials(accessKey, secretKey)
.endpoint(minioUrl)
.build();
}

}

DTO,用来传输数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileDto implements Serializable {

private static final long serialVersionUID = 232836038145089522L;

private String title;

private String description;

@SuppressWarnings("java:S1948")
private MultipartFile file;

private String url;

private Long size;

private String filename;

}

获取文件列表

创建MinioService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Slf4j
@Service
public class MinioService {

@Autowired
private MinioClient minioClient;

@Value("${minio.bucket.name}")
private String bucketName;

public List<FileDto> getListObjects() {
List<FileDto> objects = new ArrayList<>();
try {
Iterable<Result<Item>> result = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName)
.recursive(true)
.build());
for (Result<Item> item : result) {
objects.add(FileDto.builder()
.filename(item.get().objectName())
.size(item.get().size())
.url(getPreSignedUrl(item.get().objectName()))
.build());
}
return objects;
} catch (Exception e) {
log.error("Happened error when get list objects from minio: ", e);
}

return objects;
}

private String getPreSignedUrl(String filename) {
return "http://localhost:8080/file/".concat(filename);
}

}

创建Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@RestController
@RequestMapping(value = "/file")
public class FileController {

@Autowired
private MinioService minioService;

@GetMapping
public ResponseEntity<Object> getFiles() {
return ResponseEntity.ok(minioService.getListObjects());
}

}

上传文件到minio

下面代码是添加到MinioService中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public FileDto uploadFile(FileDto request) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(request.getFile().getOriginalFilename())
.stream(request.getFile().getInputStream(), request.getFile().getSize(), -1)
.build());
} catch (Exception e) {
log.error("Happened error when upload file: ", e);
}
return FileDto.builder()
.title(request.getTitle())
.description(request.getDescription())
.size(request.getFile().getSize())
.url(getPreSignedUrl(request.getFile().getOriginalFilename()))
.filename(request.getFile().getOriginalFilename())
.build();
}

对应的Controller

1
2
3
4
@PostMapping(value = "/upload")
public ResponseEntity<Object> upload(@ModelAttribute FileDto request) {
return ResponseEntity.ok().body(minioService.uploadFile(request));
}

下载文件

添加到MinioService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public InputStream getObject(String filename) {
InputStream stream;
try {
stream = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(filename)
.build());
} catch (Exception e) {
log.error("Happened error when get list objects from minio: ", e);
return null;
}

return stream;
}

Controller

1
2
3
4
5
6
7
8
@GetMapping(value = "/**")
public ResponseEntity<Object> getFile(HttpServletRequest request) throws IOException {
String pattern = (String) request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);
String filename = new AntPathMatcher().extractPathWithinPattern(pattern, request.getServletPath());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(IOUtils.toByteArray(minioService.getObject(filename)));
}

访问:http://localhost:8080/file/{filename},页面中会展示或者下载对应的文件

参考

默认情况下,MySQL数据库只允许本地连接,当然数处于安全考虑,但是对于调试或者需要远程连接的情况下,会带来不少的麻烦

本文介绍如何允许数据库的远程连接,其方法对于MariaDB数据库也适用

配置MySQL服务

编辑配置文件

1
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf

找到 bind-address配置项,修改为0.0.0.0,允许所有地址的连接,如果有特定的服务器,建议修改为指定的IP地址

1
bind-address = 0.0.0.0

在mysql8.0版本中,可能没有bind-address选项,自己添加到[mysqld]中即可

重启服务

1
2
3
4
# unubtu or Debian
sudo systemctl restart mysql
# On RedHat based distributions like CentOS
sudo systemctl restart mysqld

Mysql 授权,允许用户远程连接

1
2
3
mysql -u root -p

mysql> GRANT ALL ON database_name.* TO user_name@'ip_address' IDENTIFIED BY 'user_password';

如授权root用户可以远程登录

1
mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'youpassword' WITH GRANT OPTION;

如果是MYSQL8.0,上述命令会报如下错误:

ERROR 1410 (42000): You are not allowed to create a user with GRANT

是由于从8.0开始,创建用户需要用 CREATE USER,如下:

1
2
3
mysql> CREATE USER 'root'@'%' IDENTIFIED BY 'PASSWORD';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
mysql> FLUSH PRIVILEGES;

配置防火墙

配置防火墙,允许3306端口远程访问

iptables

1
2
3
4
# 指定所有IP均可访问
sudo iptables -A INPUT -p tcp --destination-port 3306 -j ACCEPT
# 指定IP可访问
sudo iptables -A INPUT -s 10.8.0.5 -p tcp --destination-port 3306 -j ACCEPT

UFW

Ubuntu默认防火墙

1
2
3
4
# 所有IP可访问
sudo ufw allow 3306/tcp
# 指定IP访问
sudo ufw allow from 10.8.0.5 to any port 3306

FirewallD

一般用在centos系统上

1
2
3
4
5
6
7
8
9
10
# 所有IP可访问
sudo firewall-cmd --permanent --zone=public --add-port=3306/tcp
sudo firewall-cmd --reload

# 指定IP可访问
sudo firewall-cmd --new-zone=mysqlzone --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --zone=mysqlzone --add-source=10.8.0.5/32
sudo firewall-cmd --permanent --zone=mysqlzone --add-port=3306/tcp
sudo firewall-cmd --reload

参考:

Axios js是非常流行的HTTP请求。你可以在Vue js, node js, react js中使用Axios js来启动get, post, put等请求,但如果你需要同样的要求来从API下载文件响应,并使用Axios js来进行下载,那么你如何做到这一点?我会帮你用Axios做文件下载。

Axios HTTP Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
axios({
url: 'http://localhost:8000/api/get-file',
method: 'GET',
responseType: 'blob',
}).then((response) => {
var fileURL = window.URL.createObjectURL(new Blob([response.data]));
var fileLink = document.createElement('a');

fileLink.href = fileURL;
fileLink.setAttribute('download', 'file.pdf');
document.body.appendChild(fileLink);

fileLink.click();
});

HTML Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html>
<head>
<title>Download File using Axios Vue JS? - HackTheStuff</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js" integrity="sha256-S1J4GVHHDMiirir9qsXWc8ZWw74PHHafpsHp5PXtjTs=" crossorigin="anonymous"></script>
</head>
<body>
<div id="app">
<button @click="onClick()">DownLoad</button>
</div>
<script type="text/javascript">

var app = new Vue({
el: '#app',
methods: {
onClick() {
axios({
url: 'http://localhost:8000/my.pdf',
method: 'GET',
responseType: 'blob',
}).then((response) => {
var fileURL = window.URL.createObjectURL(new Blob([response.data]));
var fileLink = document.createElement('a');

fileLink.href = fileURL;
fileLink.setAttribute('download', 'file.pdf');
document.body.appendChild(fileLink);

fileLink.click();
});
}
}
})
</script>
</body>
</html>
参考: https://hackthestuff.com/article/how-to-download-file-in-vuejs-using-axios

Java获取文件拓展名的三种方式

原生Java方式

1
2
3
4
5
6
public Optional<String> getExtensionByStringHandling(String filename) {
return Optional.ofNullable(filename)
.filter(f -> f.contains("."))
.map(f -> f.substring(filename.lastIndexOf(".") + 1));
}

特殊情况:

  • 没有拓展名:该函数返回空字符串
  • 只有拓展名(例如.gitignore):返回gitignore

使用Apache Commons IO包下的FilenameUtils.getExtension工具

1
2
3
public String getExtensionByApacheCommonLib(String filename) {
return FilenameUtils.getExtension(filename);
}

使用Guava Library

1
2
3
4
5
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1.1-jre</version>
</dependency>
1
2
3
public String getExtensionByGuava(String filename) {
return Files.getFileExtension(filename);
}

将UTC(字符串包含TZ的时间)时间转换成本地时间

1
2
3
4
5
6
utc = "2017-07-28T08:28:47.776Z"
UTC_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
utcTime = datetime.datetime.strptime(utc, UTC_FORMAT)
localtime = utcTime + datetime.timedelta(hours=8)
print(localtime)
# 输出:2017-07-28 16:28:47.776000