Freemarker和MinIO存储博客文章


运用 Freemarker 模板引擎为文章生成静态文件,将文件存放在分布式文件系统 MinIO 中,以及 Non-XML response from server问题的解决

描述:

前端传输数据给服务端,服务端保存数据库后将数据渲染到freemark文件内,生成静态文件html文件,将静态文件保存到minio中,将对应的静态文件的minio中地址保存到数据库中,来访问这个博客文章

遇到的问题(Non-XML response from server问题)

yml配置

要记住新版本minio和老版本一点也不同!!!尤其是yml相关配置~~~去看注释!!!!

老版本配置教程

  1. 首先docker拉取镜像

    1
    docker pull minio/minio:RELEASE.2021-06-17T00-10-46Z
  2. 构建容器

    1
    2
    3
    4
    5
    6
    docker run -p 9000:9000 --name minio -di --restart=always \
    -e "MINIO_ROOT_USER=minio" \
    -e "MINIO_ROOT_PASSWORD=minio@123" \
    -v /usr/local/minio/data:/data \
    -v /usr/local/minio/config:/root/.minio \
    minio/minio:RELEASE.2021-06-17T00-10-46Z server /data
  3. 访问 ip + 端口号

思路实现

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.1.0</version>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
  2. yml配置

    1
    2
    3
    4
    5
    minio:
    accessKey: minio
    secretKey: minio@123
    bucket: articles #老版本是登录账号和密码 新版本这俩是service account生成的,不是登录账号和密码
    endpoint: http://8.130.97.224:9000 #老版本是访问地址 新版本注意是api地址,不是访问网址
  3. 前端发送请求

    1
    2
    3
    4
    5
    6
    7
    {
    "id": 1,
    "title": "123",
    "content": "dada",
    "author": "kzy",
    "url": ""
    }
  4. 后端接收请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@PostMapping("/articles")
public ResponseEntity<String> createArticle(@RequestBody Article article) {
boolean flag = articleRepository.save(article);

Article savedArticle = null;
try {
if (flag) {
savedArticle = articleRepository.selectById(article.getId());
//把数据渲染ftl然后生成静态html文件
articleHtmlService.generateHtml(savedArticle);
// 更新数据库中的URL字段
savedArticle.setUrl(endpoint +"/"+ bucket + "/"+"article"+savedArticle.getId() + ".html");
articleRepository.updateById(savedArticle);
}
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>("Error generating HTML", HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>("Article saved with id: " + savedArticle.getId(), HttpStatus.CREATED);
}
  1. 创建模板文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!DOCTYPE html>
    <html>
    <head>
    <title>${article.title}</title>
    </head>
    <body>
    <h1>${article.title}</h1>
    <p>${article.content}</p>
    <footer>Written by ${article.author}</footer>
    </body>
    </html>
  2. 渲染文件和保存到minio中

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
@Autowired
private ArticleService articleService;

@Autowired
private MinioClient minioClient;

@Autowired
private Configuration configuration;

public void generateHtml(Article article) throws IOException, TemplateException {
Map<String, Object> model = new HashMap<>();
model.put("article", article);

// 使用FreeMarker渲染HTML
//先拿到模板
Template template = configuration.getTemplate("article.ftl");
//在渲染数据,生成静态文件
template.process(model, new FileWriter("d:/article.html"));

FileInputStream fileInputStream = null;

try {
fileInputStream = new FileInputStream("d:\\article.html");

// 保存到MinIO
minioClient.putObject(PutObjectArgs.builder()
.contentType("text/html")
.bucket("articles")
.object("article" + article.getId() + ".html")
.stream(fileInputStream, fileInputStream.available(), -1)
.build());
} catch (Exception e) {
e.printStackTrace();
}
}

修改和删除

采用的是删除minio中文件的文件和数据库中的url,并生成新的保存

疑问

删除比较快,但是更新比较慢,那已经删除了,那用户怎么访问到数据呢?

遇到更新操作比删除操作慢,并且担心在更新过程中旧文件被删除后新文件还未生成时,用户可能会访问到不存在的文件,这确实是一个需要考虑的问题。以下是一些策略来解决这个问题:

1. 使用版本控制

MinIO支持对象版本控制,开启版本控制后,删除操作实际上不会立即删除文件,而是将其标记为删除,同时保留历史版本。这样,在新文件生成之前,用户仍然可以访问到旧版本的文件。

2. 临时重定向

在删除旧文件后,可以设置一个临时的重定向机制,将用户请求重定向到一个占位页面或一个加载状态,直到新文件生成并上传成功。

3. 原子操作

确保更新操作的原子性,即要么完全更新成功,要么完全不更新。这通常涉及到事务管理或使用数据库和文件存储的原子操作。

4. 异步处理

将更新操作异步化,首先删除旧文件,然后立即返回成功响应给用户,后台异步生成新文件并上传到MinIO。在新文件生成之前,可以使用缓存或临时文件来处理用户请求。

5. 预生成静态文件

在更新数据库文章内容的同时,预生成新的静态文件并保存在临时位置。在确认新文件已经生成并上传成功后,再删除旧文件。

6. 使用锁机制

在更新过程中,使用锁机制来防止并发访问。当一个文件正在被更新时,其他请求会被阻塞或重定向,直到更新完成。

7. 设置合理的缓存策略

设置MinIO的缓存策略,使得即使文件被删除,用户在短时间内仍然可以从缓存中获取到内容。

8. 使用状态标记

在数据库中为文章设置一个状态标记,如pending_update,在更新过程中将状态设置为pending_update,前端根据状态决定是否显示加载提示或重定向。

例子:异步处理

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
// 更新文章的方法,异步生成和上传新文件 使用缓存和临时文件来访问
public void updateArticleAsync(Long id, Article updatedArticle) {
// 从数据库获取旧文章信息
Article originalArticle = articleRepository.findById(id).orElseThrow();

// 删除旧的MinIO文件
minioClient.removeObject("bucket-name", originalArticle.getUrl());

// 更新数据库,但不保存新的URL
originalArticle.setUrl(null);
articleRepository.save(originalArticle);

// 异步执行生成和上传新文件的操作
executorService.submit(() -> {
String newHtmlContent = generateHtmlContent(updatedArticle);
String newFilePath = "path/to/new/file.html";

// 上传新文件到MinIO
minioClient.putObject("bucket-name", newFilePath, new ByteArrayInputStream(newHtmlContent.getBytes(StandardCharsets.UTF_8)));

// 更新数据库中的URL
updatedArticle.setUrl(newFilePath);
articleRepository.save(updatedArticle);
});
}

文章作者: 小康呀
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小康呀 !
评论
 上一篇
2024-05-27 是小康呀~
下一篇 
test_my_site test_my_site
2024-05-17 是小康呀~