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

个人Blog:dykang.top

描述:

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

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

yml配置

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

老版本配置教程

  1. 首先docker拉取镜像

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

    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. 引入依赖

            <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配置

    minio:
      accessKey: minio    
      secretKey: minio@123
      bucket: articles    #老版本是登录账号和密码 新版本这俩是service account生成的,不是登录账号和密码
      endpoint: http://xxx.xxx.xxx.xxx:9000   #老版本是访问地址  新版本注意是api地址,不是访问网址

  3. 前端发送请求

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

@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. 创建模板文件

    <!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中

@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,前端根据状态决定是否显示加载提示或重定向。

例子:异步处理

// 更新文章的方法,异步生成和上传新文件 使用缓存和临时文件来访问
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);
    });
}

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值