黑马头条项目学习--Day2: app端文章查看,静态化freemarker,分布式文件系统minIO

news2024/9/22 3:59:04

app端文章

  • Day02: app端文章查看,静态化freemarker,分布式文件系统minIO
    • a. app端文章列表查询
      • 1) 需求分析
      • 2) 实现思路
    • b. app端文章详细
      • 1) 需求分析
      • 2) Freemarker概述
        • a) 基础语法种类
        • b) 集合指令(List和Map)
        • c) if指令
        • d) 运算符
        • e) 空值处理
        • f) 内建函数
        • g) 静态化测试
    • c) 对象存储服务MinIO概述
      • 1) Linux安装
      • 2) 封装MinIO为starter
        • a) 创建模块heima-file-starter
        • b) 配置类
        • c) 封装操作minIO类
        • d) 对外加入自动配置
        • e) 其他微服务使用
    • d) 实现步骤

Day02: app端文章查看,静态化freemarker,分布式文件系统minIO

a. app端文章列表查询

1) 需求分析

在这里插入图片描述

表结构分析

表名称说明
ap_article文章信息表,存储已发布的文章
ap_article_configAPP已发布文章配置表
ap_article_contentAPP已发布文章内容表
ap_authorAPP文章作者信息表
ap_collectionAPP收藏信息表

ap_article 文章基本信息表

image-20210419151839634

ap_article_config 文章配置表

image-20210419151854868

ap_article_content 文章内容表

image-20210419151912063

三张表关系分析

在这里插入图片描述

表的拆分-垂直分表

垂直分表:将一个表的字段分散到多个表中,每个表存储其中一部分字段。

优势:

  • 1.少IO争抢,减少锁表的几率,查看文章概述与文章详情互不影响
  • 2.充分发挥高频数据的操作效率,对文章概述数据操作的高效率不会被操作文章详情数据的低效率所拖累。

拆分规则:

  • 1.把不常用的字段单独放在一张表

  • 2.把text,blob等大字段拆分出来单独放在一张表

  • 3.经常组合查询的字段单独放在一张表中

2) 实现思路

  • 1.在默认频道展示10条文章信息
  • 2.可以切换频道查看不同种类文章
  • 3.当用户下拉可以加载最新的文章(分页)本页文章列表中发布时间为最大的时间为依据
  • 4.当用户上拉可以加载更多的文章信息(按照发布时间)本页文章列表中发布时间最小的时间为依据
  • 5.如果是当前频道的首页,前端传递默认参数:
    • maxBehotTime:0(毫秒)
    • minBehotTime:20000000000000(毫秒) 2063年
# 按照发布时间倒序查询10条文章 
# 频道筛选 where aa.channel_id = 1 
# 加载首页 and aa.publish_time < '2063-04-19 00:00:00'
# 加载更多 and aa.publish_time < '2020-09-07 22:30:09‘
# 加载最新 and aa.publish_time > '2020-09-07 22:30:09'
# 该文章未下架未删除
select * from ap_article aa LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
where aac.is_down != 1 and aac.is_delete != 1
and aa.channel_id = 1
and aa.publish_time < '2063-04-19 00:00:00'
order by aa.publish_time DESC
limit 10

接口定义

加载首页加载更多加载最新
接口路径/api/v1/article/load/api/v1/article/loadmore/api/v1/article/loadnew
请求方式POSTPOSTPOST
参数ArticleHomeDtoArticleHomeDtoArticleHomeDto
响应结果ResponseResultResponseResultResponseResult

ArticleHomeDto

package com.heima.model.article.dtos;

import lombok.Data;

import java.util.Date;

@Data
public class ArticleHomeDto {

    // 最大时间
    Date maxBehotTime;
    // 最小时间
    Date minBehotTime;
    // 分页size
    Integer size;
    // 频道ID
    String tag;
}

导入相关项目,及在service的pom文件下添加相关module属性

需要在nacos中添加对应的配置

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.article.pojos

定义控制器接口:接口路径、请求方式、入参、出参

@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
    
    @Autowired
    private ApArticleService apArticleService;

    /**
     * 加载首页
     * @param dto
     * @return
     */
    @PostMapping("/load")
    public ResponseResult load(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_MORE);
    }

    /**
     * 加载更多
     * @param dto
     * @return
     */
    @PostMapping("/loadmore")
    public ResponseResult loadmore(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_MORE);
    }

    /**
     * 加载最新
     * @param dto
     * @return
     */
    @PostMapping("/loadnew")
    public ResponseResult loadnew(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_NEW);
    }
}

编写mapper文件,文章表与文章配置表的多表查询

@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {
    /**
     * 加载文章列表
     * @param dto
     * @param type 1:加载更多   2:加载最新
     * @return
     */
    List<ApArticle> loadArticleList(ArticleHomeDto dto, Short type);
}

对应的映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.article.mapper.ApArticleMapper">

    <resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
        <id column="id" property="id"/>
        <result column="title" property="title"/>
        <result column="author_id" property="authorId"/>
        <result column="author_name" property="authorName"/>
        <result column="channel_id" property="channelId"/>
        <result column="channel_name" property="channelName"/>
        <result column="layout" property="layout"/>
        <result column="flag" property="flag"/>
        <result column="images" property="images"/>
        <result column="labels" property="labels"/>
        <result column="likes" property="likes"/>
        <result column="collection" property="collection"/>
        <result column="comment" property="comment"/>
        <result column="views" property="views"/>
        <result column="province_id" property="provinceId"/>
        <result column="city_id" property="cityId"/>
        <result column="county_id" property="countyId"/>
        <result column="created_time" property="createdTime"/>
        <result column="publish_time" property="publishTime"/>
        <result column="sync_status" property="syncStatus"/>
        <result column="static_url" property="staticUrl"/>
    </resultMap>
    <select id="loadArticleList" resultMap="resultMap">
        SELECT
        aa.*
        FROM
        `ap_article` aa
        LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
        <where>
            and aac.is_delete != 1
            and aac.is_down != 1
            <!-- loadmore -->
            <if test="type != null and type == 1">
                and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}
            </if>
            <if test="type != null and type == 2">
                and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}
            </if>
            <if test="dto.tag != '__all__'">
                and aa.channel_id = #{dto.tag}
            </if>
        </where>
        order by aa.publish_time desc
        limit #{dto.size}
    </select>
</mapper>

定义常量类

public class ArticleConstants {
    public static final Short LOADTYPE_LOAD_MORE = 1;
    public static final Short LOADTYPE_LOAD_NEW = 2;
    public static final String DEFAULT_TAG = "__all__";
}

编写业务层代码

@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    private final static short MAX_PAGE_SIZE = 50;

    /**
     * 加载文章列表
     * @param dto
     * @param type 1:加载更多   2:加载最新
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, Short type) {
        // 1.校验参数
        // 分页条数的校验
        Integer size = dto.getSize();
        if (size == null || size == 0) {
            size = 10; // 若没有值,赋予默认值10
        }
        // 分页值不能超过50
        size = Math.min(size, MAX_PAGE_SIZE);
        dto.setSize(size);

        // 校验type参数
        if (!type.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && !type.equals(ArticleConstants.LOADTYPE_LOAD_NEW)) {
            type = ArticleConstants.LOADTYPE_LOAD_MORE; // 若没有值,赋予默认值1
        }

        // 频道参数校验
        if (StringUtils.isBlank(dto.getTag())) {
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }

        // 时间校验
        if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());

        // 2.查询
        List<ApArticle> apArticleList = apArticleMapper.loadArticleList(dto, type);

        // 3.结果返回
        return ResponseResult.okResult(apArticleList);
    }
}

b. app端文章详细

1) 需求分析

在这里插入图片描述

2) Freemarker概述

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

在这里插入图片描述

技术说明
JspJsp 为 Servlet 专用,不能单独进行使用
VelocityVelocity从2010年更新完 2.0 版本后,7年没有更新。Spring Boot 官方在 1.4 版本后对此也不在支持
thmeleaf新技术,功能较为强大,但是执行的效率比较低
freemarker性能好,强大的模板语言、轻量

a) 基础语法种类

1、注释,即<#-- -->,介于其之间的内容会被freemarker忽略

<#--我是一个freemarker注释-->

2、插值(Interpolation):即 ${..} 部分,freemarker会用真实的值代替**${..}**

Hello ${name}

3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。

<# >FTL指令</#> 

4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容。

<#--freemarker中的普通文本-->
我是一个普通的文本

b) 集合指令(List和Map)

实例代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
    
<#-- list 数据的展示 -->
<b>展示list中的stu数据:</b>
<br>
<br>
<table>
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stus as stu>
        <tr>
            <td>${stu_index+1}</td>
            <td>${stu.name}</td>
            <td>${stu.age}</td>
            <td>${stu.money}</td>
        </tr>
    </#list>

</table>
<hr>
    
<#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:${stuMap['stu1'].name}<br/>
年龄:${stuMap['stu1'].age}<br/>
<br/>
<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:${stuMap.stu2.name}<br/>
年龄:${stuMap.stu2.age}<br/>

<br/>
<a href="###">遍历map中两个学生信息:</a><br/>
<table>
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stuMap?keys as key >
        <tr>
            <td>${key_index}</td>
            <td>${stuMap[key].name}</td>
            <td>${stuMap[key].age}</td>
            <td>${stuMap[key].money}</td>
        </tr>
    </#list>
</table>
<hr>
 
</body>
</html>

👆上面代码解释:

${key_index}:
index:得到循环的下标,使用方法是在stu后边加"_index",它的值是从0开始

c) if指令

​ if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不再输出。

  • 指令格式
<#if ></if>

实例代码:

<table>
    <tr>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stus as stu >
        <#if stu.name='小红'>
            <tr style="color: red">
                <td>${stu_index}</td>
                <td>${stu.name}</td>
                <td>${stu.age}</td>
                <td>${stu.money}</td>
            </tr>
            <#else >
            <tr>
                <td>${stu_index}</td>
                <td>${stu.name}</td>
                <td>${stu.age}</td>
                <td>${stu.money}</td>
            </tr>
        </#if>
    </#list>
</table>

d) 运算符

1、算数运算符

FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:

  • 加法: +
  • 减法: -
  • 乘法: *
  • 除法: /
  • 求模 (求余): %

模板代码

<b>算数运算符</b>
<br/><br/>
    100+5 运算:  ${100 + 5 }<br/>
    100 - 5 * 5运算:${100 - 5 * 5}<br/>
    5 / 2运算:${5 / 2}<br/>
    12 % 10运算:${12 % 10}<br/>
<hr>

除了 + 运算以外,其他的运算只能和 number 数字类型的计算。

2、比较运算符

  • =或者==:判断两个值是否相等.
  • !=:判断两个值是否不等.
  • >或者gt:判断左边值是否大于右边值
  • >=或者gte:判断左边值是否大于等于右边值
  • <或者lt:判断左边值是否小于右边值
  • <=或者lte:判断左边值是否小于等于右边值

比较运算符注意

  • **=!=**可以用于字符串、数值和日期来比较是否相等
  • **=!=**两边必须是相同类型的值,否则会产生错误
  • 字符串 "x""x " 、**"X"**比较是不等的.因为FreeMarker是精确比较
  • 其它的运行符可以作用于数字和日期,但不能作用于字符串
  • 使用**gt等字母运算符代替>会有更好的效果,因为 FreeMarker会把>**解释成FTL标签的结束字符
  • 可以使用括号来避免这种情况,如:<#if (x>y)>

3、逻辑运算符

  • 逻辑与:&&
  • 逻辑或:||
  • 逻辑非:!

逻辑运算符只能作用于布尔值,否则将产生错误 。

e) 空值处理

1、判断某变量是否存在使用 “??”

用法为:variable??,如果该变量存在,返回true,否则返回false

例:为防止stus为空报错可以加上判断如下:

    <#if stus??>
    <#list stus as stu>
    	......
    </#list>
    </#if>

2、缺失变量默认值使用 “!”

  • 使用!要以指定一个默认值,当变量为空时显示默认值

    例: ${name!‘’}表示如果name为空显示空字符串。

  • 如果是嵌套对象则建议使用()括起来

    例: ${(stu.bestFriend.name)!‘’}表示,如果stu或bestFriend或name为空默认显示空字符串。

f) 内建函数

内建函数语法格式: 变量+?+函数名称

1、和到某个集合的大小

${集合名?size}

2、日期格式化

显示年月日: ${today?date}
显示时分秒:${today?time}
显示日期+时间:${today?datetime}
自定义格式化: ${today?string("yyyy年MM月")}

3、内建函数c

model.addAttribute(“point”, 10292012278902L);

point是数字型,使用${point}会显示这个数字的值,每三位使用逗号分隔。

如果不想显示为每三位分隔的数字,可以使用c函数将数字型转成字符串输出

${point?c}

4、将json字符串转成对象

一个例子:

其中用到了 assign标签,assign的作用是定义一个变量。

<#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
<#assign data=text?eval />
开户行:${data.bank}  账号:${data.account}

g) 静态化测试

使用Freemarker原生Api来生成静态内容

@SpringBootTest(classes = FreemarkerDemoApplication.class)
@RunWith(SpringRunner.class)
public class FreemarkerTest {

    @Autowired
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
        Template template = configuration.getTemplate("02-list.ftl");

        /**
         * 合成方法
         *
         * 第一个参数:模型数据
         * 第二个参数:输出流
         */
        template.process(getData(), new FileWriter("F:/list.html"));
    }

    private Map getData(){
        Map<String, Object> map = new HashMap<>();
        Student stu1 = new Student();
        stu1.setName("小强");
        stu1.setAge(18);
        stu1.setMoney(1000.86f);
        stu1.setBirthday(new Date());

        //小红对象模型数据
        Student stu2 = new Student();
        stu2.setName("小红");
        stu2.setMoney(200.1f);
        stu2.setAge(19);

        //将两个对象模型数据存放到List集合中
        List<Student> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

        //向model中存放List集合数据
        map.put("stus",stus);

        Map<String, Student> stuMap = new HashMap<>();
        stuMap.put("stu1",stu1);
        stuMap.put("stu2",stu2);

        //向model中存放Set集合数据
        map.put("stuMap",stuMap);

        return map;
    }
}

c) 对象存储服务MinIO概述

对象存储的方式对比

存储方式优点缺点
服务器磁盘开发便捷,成本低扩展困难
分布式文件系统容易实现扩容复杂度高
第三方存储开发简单,功能强大,免维护收费

分布式文件系统

存储方式优点缺点
FastDFS1.主备服务,高可用。2.支持主从文件,支持自定义扩展名。3.支持动态扩容1,没有完备官方文档,近几年没有更新2,环境搭建较为麻烦
MinIO1.性能高,准硬件条件下它能达到55GB/s的读、35GB/s的写速率。2.部署自带管理界面。3.MinIO.Inc运营的开源项目,社区活跃度高。4.提供了所有主流开发语言的SDK1,不支持动态增加节点

1) Linux安装

1.拉取镜像

docker pull minio/minio

2.创建容器

docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data

3.访问minio系统

http://192.168.200.130:9000

基本概念

  • bucket – 类比于文件系统的文件夹
  • Object – 类比文件系统的文件
  • Keys – 类比文件名

2) 封装MinIO为starter

a) 创建模块heima-file-starter

导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>7.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

b) 配置类

MinIOConfigProperties

package com.heima.file.config;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.io.Serializable;

@Data
@ConfigurationProperties(prefix = "minio")  // 文件上传 配置前缀file.oss
public class MinIOConfigProperties implements Serializable {

    private String accessKey;
    private String secretKey;
    private String bucket;
    private String endpoint;
    private String readPath;
}

MinIOConfig

package com.heima.file.config;

import com.heima.file.service.FileStorageService;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Data
@Configuration
@EnableConfigurationProperties({MinIOConfigProperties.class})
//当引入FileStorageService接口时
@ConditionalOnClass(FileStorageService.class)
public class MinIOConfig {

   @Autowired
   private MinIOConfigProperties minIOConfigProperties;

    @Bean
    public MinioClient buildMinioClient(){
        return MinioClient
                .builder()
                .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
                .endpoint(minIOConfigProperties.getEndpoint())
                .build();
    }
}

c) 封装操作minIO类

FileStorageService

package com.heima.file.service;

import java.io.InputStream;

/**
 * @author itheima
 */
public interface FileStorageService {


    /**
     *  上传图片文件
     * @param prefix  文件前缀
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  文件全路径
     */
    public String uploadImgFile(String prefix, String filename,InputStream inputStream);

    /**
     *  上传html文件
     * @param prefix  文件前缀
     * @param filename   文件名
     * @param inputStream  文件流
     * @return  文件全路径
     */
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);

    /**
     * 删除文件
     * @param pathUrl  文件全路径
     */
    public void delete(String pathUrl);

    /**
     * 下载文件
     * @param pathUrl  文件全路径
     * @return
     *
     */
    public byte[]  downLoadFile(String pathUrl);

}

MinIOFileStorageService

package com.heima.file.service.impl;


import com.heima.file.config.MinIOConfig;
import com.heima.file.config.MinIOConfigProperties;
import com.heima.file.service.FileStorageService;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

@Slf4j
@EnableConfigurationProperties(MinIOConfigProperties.class)
@Import(MinIOConfig.class)
public class MinIOFileStorageService implements FileStorageService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    private final static String separator = "/";

    /**
     * @param dirPath
     * @param filename  yyyy/mm/dd/file.jpg
     * @return
     */
    public String builderFilePath(String dirPath,String filename) {
        StringBuilder stringBuilder = new StringBuilder(50);
        if(!StringUtils.isEmpty(dirPath)){
            stringBuilder.append(dirPath).append(separator);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        String todayStr = sdf.format(new Date());
        stringBuilder.append(todayStr).append(separator);
        stringBuilder.append(filename);
        return stringBuilder.toString();
    }

    /**
     *  上传图片文件
     * @param prefix  文件前缀
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  文件全路径
     */
    @Override
    public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType("image/jpg")
                    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator+minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString();
        }catch (Exception ex){
            log.error("minio put file error.",ex);
            throw new RuntimeException("上传文件失败");
        }
    }

    /**
     *  上传html文件
     * @param prefix  文件前缀
     * @param filename   文件名
     * @param inputStream  文件流
     * @return  文件全路径
     */
    @Override
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType("text/html")
                    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator+minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString();
        }catch (Exception ex){
            log.error("minio put file error.",ex);
            ex.printStackTrace();
            throw new RuntimeException("上传文件失败");
        }
    }

    /**
     * 删除文件
     * @param pathUrl  文件全路径
     */
    @Override
    public void delete(String pathUrl) {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
        int index = key.indexOf(separator);
        String bucket = key.substring(0,index);
        String filePath = key.substring(index+1);
        // 删除Objects
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
        try {
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            log.error("minio remove file error.  pathUrl:{}",pathUrl);
            e.printStackTrace();
        }
    }


    /**
     * 下载文件
     * @param pathUrl  文件全路径
     * @return  文件流
     *
     */
    @Override
    public byte[] downLoadFile(String pathUrl)  {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
        int index = key.indexOf(separator);
        String bucket = key.substring(0,index);
        String filePath = key.substring(index+1);
        InputStream inputStream = null;
        try {
            inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
        } catch (Exception e) {
            log.error("minio down file error.  pathUrl:{}",pathUrl);
            e.printStackTrace();
        }

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while (true) {
            try {
                if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
            } catch (IOException e) {
                e.printStackTrace();
            }
            byteArrayOutputStream.write(buff, 0, rc);
        }
        return byteArrayOutputStream.toByteArray();
    }
}

d) 对外加入自动配置

在resources中新建META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.file.service.impl.MinIOFileStorageService

e) 其他微服务使用

第一,导入heima-file-starter的依赖

第二,在微服务中添加minio所需要的配置

minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.200.130:9000
  readPath: http://192.168.200.130:9000

第三,在对应使用的业务类中注入FileStorageService,样例如下:

package com.heima.minio.test;


import com.heima.file.service.FileStorageService;
import com.heima.minio.MinioApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

@SpringBootTest(classes = MinioApplication.class)
@RunWith(SpringRunner.class)
public class MinioTest {

    @Autowired
    private FileStorageService fileStorageService;

    @Test
    public void testUpdateImgFile() {
        try {
            FileInputStream fileInputStream = new FileInputStream("E:\\tmp\\ak47.jpg");
            String filePath = fileStorageService.uploadImgFile("", "ak47.jpg", fileInputStream);
            System.out.println(filePath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

d) 实现步骤

在这里插入图片描述

1.在article微服务中添加MinIO和freemarker的依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency>
        <groupId>com.heima</groupId>
        <artifactId>heima-file-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

2.资料中找到模板文件(article.ftl)拷贝到article微服务下

3.资料中找到index.js和index.css两个文件手动上传到MinIO中

4.在artile微服务中新增测试类(后期新增文章的时候创建详情静态页,目前暂时手动生成)

  • 新建ApArticleContentMapper
@Mapper
public interface ApArticleContentMapper extends BaseMapper<ApArticleContent> {
}
  • 在artile微服务中新增测试类(后期新增文章的时候创建详情静态页,目前暂时手动生成)
@SpringBootTest(classes = ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {

    @Autowired
    private Configuration configuration;

    @Autowired
    private FileStorageService fileStorageService;


    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    @Test
    public void createStaticUrlTest() throws Exception {
        //1.获取文章内容
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, 1390536764510310401L));
        if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){
            //2.文章内容通过freemarker生成html文件
            Template template = configuration.getTemplate("article.ftl");
			// 数据模型
            Map<String, Object> params = new HashMap<>();
            params.put("content", JSONArray.parseArray(apArticleContent.getContent()));
			// 输出流
            StringWriter out = new StringWriter();
            template.process(params, out);
            
            //3.把html文件上传到minio中
            InputStream is = new ByteArrayInputStream(out.toString().getBytes());
            String path = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", is);

            //4.修改ap_article表,保存static_url字段
            ApArticle article = new ApArticle();
            article.setId(apArticleContent.getArticleId());
            article.setStaticUrl(path);
            apArticleMapper.updateById(article);

        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/855696.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vscode 搭建STM32开发环境

1.需要软件 1.1 vscode 1.2 STM32CubeMX&#xff0c;这个不是必须的&#xff0c;我是为了方便生成STM32代码 2.vscode配置 2.1安装keil Assistant 2.2配置keil Assistant 3.STMCUBE生成个STM32代码 &#xff0c;如果有自己的代码可以忽略 4.代码添加到vscode&#xff0c;并…

资深媒体人宋繁银加入《数据猿》任总编辑,全面负责公司整体内容工作

大数据产业创新服务媒体 ——聚焦数据 改变商业 2023年7月北京&#xff0c;《数据猿》宣布正式任命宋繁银为总编辑&#xff0c;全面负责公司整体内容工作。此次重要的人事任命标志着《数据猿》的发展迈上了一个新的台阶&#xff0c;对于《数据猿》团队而言&#xff0c;不仅是一…

(原创)Flutter与Native页面互相跳转

前言 实际开发混合项目时&#xff0c;常常会有页面跳转的需求 如果是原生界面和flutter界面需要互相跳转 这种情况应该怎么处理呢&#xff1f; 今天这篇博客主要就来介绍下这个情况 其实想一下&#xff0c;这个问题可以拆成四个小的问题来分析&#xff1a; 1&#xff1a;原生界…

【工作记录】docker安装gitlab、重置密码@20230809

前言 本文记录下基于docker安装gitlab并重置管理员密码的过程。 作为记录的同时也希望能帮助到需要的朋友们。 搭建过程 1. 准备好docker环境并启动docker [rootslave-node1 docker-gitlab]# docker version Client:Version: 18.06.1-ceAPI version: 1.38…

Profibus-DP转modbus RTU网关modbus rtu和tcp的区别

捷米JM-DPM-RTU网关在Profibus总线侧实现主站功能&#xff0c;在Modbus串口侧实现从站功能。可将ProfibusDP协议的设备&#xff08;如&#xff1a;EH流量计、倍福编码器等&#xff09;接入到Modbus网络中&#xff1b;通过增加DP/PA耦合器&#xff0c;也可将Profibus PA从站接入…

智能质检技术的核心环节:语音识别和自然语言处理

随着呼叫中心行业的快速发展和客户服务需求的不断提高&#xff0c;越来越多的企业开始采用智能质检技术&#xff0c;以提高呼叫中心的质量和效率。而在智能质检技术中&#xff0c;语音识别和自然语言处理是其核心环节&#xff0c;对于提高质检的准确性和效率具有重要作用。 语音…

el-select与el-tree结合使用,实现select框下拉使用树形结构选择数据

使用el-select与el-tree&#xff0c;实现如下效果&#xff0c; 代码如下&#xff1a; 注意点&#xff1a;搜索input框的代码一点放在option上面&#xff0c;不要放在option里面&#xff0c;否则一点击搜索框&#xff0c;下拉框就会收起来&#xff0c;不能使用。 <el-select…

基于 JMeter API 开发性能测试平台

目录 背景&#xff1a; 常用的 JMeter 类和功能的解释&#xff1a; JMeter 编写性能测试脚本的大致流程示意图&#xff1a; 源码实现方式&#xff1a; (1) 环境初始化 (2) 环境初始化 (3) 创建测试计划 (4) 创建 ThreadGroup (5) 创建循环控制器 (6) 创建 Sampler (…

ArcGIS Pro暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例应用

GIS是利用电子计算机及其外部设备&#xff0c;采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲&#xff0c;它是在一定的地域内&#xff0c;将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来&#xff0c;达到对地理和属性信息的综合管理。GIS的…

数据挖掘具体步骤

数据挖掘具体步骤 1、理解业务与数据 2、准备数据 数据清洗&#xff1a; 缺失值处理&#xff1a; 异常值: 数据标准化&#xff1a; 特征选择&#xff1a; 数据采样处理&#xff1a; 3、数据建模 分类问题&#xff1a; 聚类问题&#xff1a; 回归问题 关联分析 集成学习 image B…

linux - 用户权限

认知root用户 无论是Windows、Macos、Linux均采用多用户的管理模式进行权限管理 在Linux系统中,拥有最大权限的账户名为: root(超级管理员) root用户拥有最大的系统操作权限&#xff0c;而普通用户在许多地方的权限是受限的。普通用户的权限&#xff0c;一般在其HOME目录内是不…

Simulation 线性静力分析流程

有限元仿真分析软件有很多&#xff0c;但是分析的流程却是大同小异&#xff0c;今天给大家分享的是Simulation的线性静力分析流程。 1.构思分析方案。 确定研究对象&#xff0c;研究的方法、验证方案等等。听起来比较空洞&#xff0c;实践过程中我建议首先需要把目标和有限元分…

24届近5年东南大学自动化考研院校分析

今天给大家带来的是东南大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、东南大学 学校简介 东南大学是我国最早建立的高等学府之一&#xff0c;素有“学府圣地”和“东南学府第一流”之美誉。东南大学前身是创建于1902年的三江师范学堂。1921年经近代著名教育家…

软件测试的49种方法

1. α测试_Alpha测试 α测试&#xff0c;英文是Alpha testing。又称Alpha测试。 Alpha测试是由一个用户在开发环境下进行的测试&#xff0c;也可以是公司内部的用户在模拟实际操作环境下进行的受控测试&#xff0c;Alpha测试不能由该系统的程序员或测试员完成。 在系统开发接近…

【BASH】回顾与知识点梳理(十四)

【BASH】回顾与知识点梳理 十四 十四. 文件与目录的默认权限与隐藏权限14.1 文件预设权限&#xff1a;umaskumask 的利用与重要性&#xff1a;专题制作 14.2 文件隐藏属性chattr (配置文件案隐藏属性)lsattr (显示文件隐藏属性) 14.3 文件特殊权限&#xff1a; SUID, SGID, SBI…

短视频内容平台(如TikTok、Instagram Reel、YouTube Shorts)的系统设计

现在&#xff0c;短视频内容已成为新趋势&#xff0c;每个人都在从TikTok、Instagram、YouTube等平台上消费这些内容。让我们看看如何为TikTok创建一个系统。 这样的应用程序看起来很小&#xff0c;但在后台有很多事情正在进行。以下是相关的挑战&#xff1a; •由于该应用程序…

【Linux命令行与Shell脚本编程】第十八章 文本处理与编辑器基础

Linux命令行与Shell脚本编程 第十八章 文本处理与编辑器基础 文章目录 Linux命令行与Shell脚本编程第十八章 文本处理与编辑器基础 文本处理与编辑器基础8.1.文本处理8.1.1.sed编辑器8.1.1.1.在命令行中定义编辑器命令8.1.1.2.在命令行中使用多个编辑器命令8.1.1.3.从文件中读…

项目8.9总结

这两天写了评论和用户之间的关注&#xff0c;然后就是歌曲的上传&#xff0c;歌曲的上传很简陋&#xff0c;并且还有点问题 关注 歌曲上传

[Vue] Vue2和Vue3的生命周期函数

vue2有11个生命周期钩子, vue3有8个生命周期钩子 从vue创建、运行、到销毁总是伴随着各种事件, 创建、挂载、更新到销毁。 1.vue2系列生命周期 ⑴【beforecreate】实例创建前。 vue完全创建之前&#xff0c;会自动执行这个函数。 ⑵【Created】实例创建后。 这也是个生命…

OKHTTP缓存问题记录

使用okhttp的缓存功能 okhttp有一个功能就是适配http协议里面cache-control头 但是在实际使用的过程中&#xff0c;发现非文本内容&#xff0c;如果不对Response中的inputstream读取&#xff0c;就不会缓存下来 比如使用如下代码读取后才能缓存到cache目录 ResponseBody bod…