黑马头条-day02

系列文章目录
黑马头条-day01
黑马头条-day02



app端文章查看

1、需求分析

文章布局展示
在这里插入图片描述

2、接口定义

在这里插入图片描述
1、默认展示推荐频道数据
2、可以切换频道查看不同频道下的文章
3、当用户下拉可以加载最新的文章,大于最大时间。
4、当用户上拉可以加载更多的文章信息(历史数据),小于最小时间。
在这里插入图片描述
ArticleHomeDto:

@Data
public class ArticleHomeDto {
    // 最大时间
    Date maxBehotTime;
    // 最小时间
    Date minBehotTime;
    // 分页size
    Integer size;
    // 频道ID
    String tag;
}

3、表结构分析

ap_article 文章基本信息表
在这里插入图片描述

ap_article_config 文章配置表
在这里插入图片描述
ap_article_content 文章内容表
在这里插入图片描述

三张表关系分析 (分表)
在这里插入图片描述

拆分的意义?

1、首页不需要看文章内容,只需要读取文章基本信息和文章配置表即可,这样可以加快首页读取效率
2、查看文章详情仅需要内容即可,只需要读取文章内容表,这样就无需查询主表,提高效率
3、文章操作(上下架,评论)如果比较频繁,也可以单独拆出来,减少对主表的影响,所以,文章配置表就单独拆出来了

4、功能实现

4.1 创建启动类及相关配置文件

启动类

@SpringBootApplication
public class ArticleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ArticleApplication.class,args);
    }
}

bootstrap.yml

server:
  port: 51802
spring:
  application:
    name: leadnews-article
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.130:8848 #注册中心
      config:
        server-addr: 192.168.200.130:8848 #配置中心
        file-extension: yml
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

nacos配置 leadnews-article

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.200.130:3306/leadnews_article?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root

逆向工程生成代码。

4.2 具体业务实现

定义接口
在这里插入图片描述

@RestController
@RequestMapping("/api/v1/article")
public class ApArticleController {


    @PostMapping("/load")
    public ResponseResult load(@RequestBody ArticleHomeDto dto) {
        return apArticleService.loadArticleList(ArticleConstants.LOADTYPE_LOAD_MORE, dto);
    }

    @PostMapping("/loadmore")
    public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
        return apArticleService.loadArticleList(ArticleConstants.LOADTYPE_LOAD_MORE, dto);
    }

    @PostMapping("/loadnew")
    public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
        return apArticleService.loadArticleList(ArticleConstants.LOADTYPE_LOAD_NEW, dto);
    }
}

业务层代码
接口

public interface ApArticleService extends IService<ApArticle> {

    /**
     * 根据参数加载文章列表
     * @param loadtype 1为加载更多  2为加载最新
     * @param dto
     * @return
     */
    ResponseResult load(ArticleHomeDto dto,int loadtype);

}

实现类

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

  
    @Autowired
    private ApArticleMapper apArticleMapper;

    /**
     * 根据参数加载文章列表
     * @param loadtype 1为加载更多  2为加载最新
     * @param dto
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, int loadtype) {
        ResponseResult responseResult = ResponseResult.okResult(apArticleMapper.loadArticleList(dto, loadtype));
        return responseResult;
    }
    
}

mapper

@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {

	/**
	 * 查询文章列表
	 * @param type
	 * @param dto
	 * @return
	 */
	List<ApArticle> loadArticleList(@Param("type") int type, @Param("dto") ArticleHomeDto dto);
}

映射文件

<?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="BaseResultMap" 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="origin" property="origin" />
        <result column="static_url" property="staticUrl" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, title, author_id, author_name, channel_id, channel_name, layout, flag, images, labels, likes, collection, comment, views, province_id, city_id, county_id, created_time, publish_time, sync_status, origin, static_url
    </sql>

    <select id="loadArticleList" resultMap="BaseResultMap">
        SELECT
            aa.*
        FROM
            `ap_article` aa
                LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
         where  aac.is_delete != 1
           and aac.is_down != 1
        <!-- loadmore -->
        <!--当type等于1,代表加载更多-->
        <if test="type != null and type == 1">
        	<!--根据发布时间小于文章最小时间,这里要转义小于号-->
            and aa.publish_time &lt; #{dto.minBehotTime}
        </if>
        <!-- loadnew -->
        <!--当type等于2,代表加载更新-->
        <if test="type != null and type == 2">
        	<!--根据发布时间大于文章最小时间-->
            and aa.publish_time &gt; #{dto.maxBehotTime}
        </if>
        <!--当tag的值不等于all的时候说明要根据id查询频道-->
        <if test="dto.tag != '__all__'">
            and aa.channel_id = #{dto.tag}
        </if>
        order by aa.publish_time desc
        limit #{dto.size}
    </select>
</mapper>

4.3 前后端联调测试

在app网关微服务的nacos配置中心添加文章微服务的路由。

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      routes:
        # 用户微服务
        - id: user
          uri: lb://leadnews-user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix= 1
        # 文章微服务
        - id: article
          uri: lb://leadnews-article
          predicates:
            - Path=/article/**
          filters:
            - StripPrefix= 1

第二:启动nginx,直接使用前端项目测试,启动文章微服务,用户微服务、app网关微服务
第三:访问 http://app.itcast.cn/ 测试黑马头条APP端文章列表查询效果

文章详情

实现方案1

用户某一条文章,根据文章的id去查询文章内容表,返回渲染页面
在这里插入图片描述

实现方案2-静态模板展示

根据文章内容通过模板技术生成静态的html文件
在这里插入图片描述

方案二的实现步骤

在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>

nacos配置文件增加leadnews-article的配置

  freemarker:
    cache: false  #关闭模板缓存,方便测试
    settings:
      template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl               #指定Freemarker模板文件的后缀名
    template-loader-path: classpath:/templates

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

将文章模版文件拷贝到微服务下

在这里插入图片描述

新增测试类

/**
 * 测试生成文章详情页并上传
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class ArticleHtmlTest {

    @Autowired
    private ApArticleContentService apArticleContentService;

    @Autowired
    private Configuration configuration;

    @Autowired
    private FileStorageService fileStorageService;

    @Autowired
    private ApArticleService apArticleService;

    @Test
    public void testGenerateHtmlAndUpload() throws IOException, TemplateException {
        //1. 为文章详情的ftl模板准备map数据
        //1.1 根据文章ID查询文章详情对象
        ApArticleContent apArticleContent = apArticleContentService.getOne(Wrappers.<ApArticleContent>lambdaQuery()
                .eq(ApArticleContent::getArticleId, 1501404634026209282L));

        //1.2 将content字符串转为数组对象
        JSONArray content = JSONArray.parseArray(apArticleContent.getContent());

        //1.3 将数组对象封装到MAP中
        Map data = new HashedMap();
        data.put("content",content);


        //2. 使用template生成详情页数据到输出流
        //2.1 构建空的输出流
        StringWriter out = new StringWriter();
        //2.2 使用configuration获取template对象
        Template template = configuration.getTemplate("article.ftl");
        //2.3 生成数据写入到输出流
        template.process(data,out);

        //3. 使用fileStorageService将文件输入流上传,得到URL
        //3.1 将输出流转为输入流
        ByteArrayInputStream in = new ByteArrayInputStream(out.toString().getBytes(StandardCharsets.UTF_8));

        //3.2 上传输入流获取URL
        String url = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", in);

        //4.更新URL到ap_article表
        ApArticle apArticle = new ApArticle();
        apArticle.setId(apArticleContent.getArticleId()); //更新条件
        apArticle.setStaticUrl(url); //更新的内容
        apArticleService.updateById(apArticle); // update ap_article set static_url=? where id=?
    }
}

freemarker

1、什么是freemarker?

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。
在这里插入图片描述
常用的java模板引擎还有哪些?
Jsp、Freemarker、Thymeleaf 、Velocity 等。

1.Jsp 为 Servlet 专用,不能单独进行使用。
2.Thymeleaf 为新技术,功能较为强大,但是执行的效率比较低。
3.Velocity从2010年更新完 2.0 版本后,便没有在更新。Spring Boot 官方在 1.4 版本后对此也不在支持,虽然 Velocity 在 2017 年版本得到迭代,但为时已晚。

2、环境搭建和快速入门

freemarker作为springmvc一种视图格式,默认情况下SpringMVC支持freemarker视图格式。

freemarker依赖包说明

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

freemarker配置application.yml说明

port: 8881 #服务端口
spring:
  application:
    name: freemarker-demo #指定服务名
  freemarker:
    cache: false  #关闭模板缓存,方便测试
    settings:
      template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl               #指定Freemarker模板文件的后缀名
    template-loader-path: classpath:/templates   #模板存放位置n;

01-basic.ftl 内容如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>
</body>
</html>

在这里插入图片描述
注意:因为controller返回的不在是json而是字符串了,所以一定要用@Controller注解
启动freemarker-demo 工程中 FreemarkerDemoApplication类
请求:http://localhost:8881/basic
在这里插入图片描述

3、freemarker语法总结

3.1 基础语法种类

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

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

2、插值(Interpolation):即 . . 部分 , f r e e m a r k e r 会用真实的值代替 {..} 部分,freemarker会用真实的值代替 ..部分,freemarker会用真实的值代替{…}

Hello ${name}

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

<# >FTL指令</#> 

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

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

3.2 freemarker基本指令

assign指令

assign指令用于在页面上定义一个变量
1、定义简单类型

<#assign name="tp">
姓名:${name}

2、定义对象类型

<#assign info={"name":"tp",'sex':'man'}>
姓名:${info.name} 性别:${info.sex}
if指令

指令格式

<#if ></if>

示例

<#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>
Student stu = new Student();
        stu.setName("小红");
        stu.setMoney(200.1f);
        stu.setAge(19);
model.addAttribute("stu",stu);
list指令和map指令

list指令和map指令用于遍历
1、在模板文件中使用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>

2、在java代码中为stus赋值

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集合数据
model.addAttribute("stus",stus);
//map数据
Map<String,Student> stuMap = new HashMap<>();
stuMap.put("stu1",stu1);
stuMap.put("stu2",stu2);

model.addAttribute("stuMap",stuMap);
//日期
model.addAttribute("today",new Date());

//长数值
model.addAttribute("point",38473897438743L);

注意:${k_index}:表示得到循环的下标,从0开始

include指令

include指令用于模板文件的嵌套

<#include "test.ftl">
指令的嵌套

例如在list指令里嵌套if指令

3.3 空值处理

1、判断某变量是否存在使用 “??”
例:为防止stus为空报错可以加上判断如下:

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

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

  • 使用!要以指定一个默认值,当变量为空时显示默认值
    例: ${name!‘’}表示如果name为空显示空字符串
  • 如果是嵌套对象则建议使用()括起来
    例: ${(stu.bestFriend.name)!‘’}表示,如果stu或bestFriend或name为空默认显示空字符串。

3.4 常见的内建函数

内建函数很想java中的方法,使用?代替.来访问他们
内建函数语法格式: 变量+?+函数名称

1、和到某个集合的大小

${集合名?size}

2、日期格式化

显示年月日: ${today?date}

显示时分秒:${today?time}

显示日期+时间:${today?datetime}

自定义格式化: ${today?string("yyyy年MM月")}

3、内建函数c

model.addAttribute(“point”, 102920122);

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

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

${point?c}

4、将json字符串转成对象

<#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
    <#assign data=text?eval />

内建函数模板页面

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>inner Function</title>
</head>
<body>

    <b>获得集合大小</b><br>

    集合大小:${stus?size}
    <hr>


    <b>获得日期</b><br>

    显示年月日: ${today?date}       <br>

    显示时分秒:${today?time}<br>

    显示日期+时间:${today?datetime}<br>

    自定义格式化:  ${today?string("yyyy年MM月")}<br>

    <hr>

    <b>内建函数C</b><br>
    没有C函数显示的数值:${point} <br>

    有C函数显示的数值:${point?c}

    <hr>

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

<hr>
</body>
</html>

内建函数Controller数据模型:

@GetMapping("innerFunc")
public String testInnerFunc(Model model) {
    //1.1 小强对象模型数据
    Student stu1 = new Student();
    stu1.setName("小强");
    stu1.setAge(18);
    stu1.setMoney(1000.86f);
    stu1.setBirthday(new Date());
    //1.2 小红对象模型数据
    Student stu2 = new Student();
    stu2.setName("小红");
    stu2.setMoney(200.1f);
    stu2.setAge(19);
    //1.3 将两个对象模型数据存放到List集合中
    List<Student> stus = new ArrayList<>();
    stus.add(stu1);
    stus.add(stu2);
    model.addAttribute("stus", stus);
    // 2.1 添加日期
    Date date = new Date();
    model.addAttribute("today", date);
    // 3.1 添加数值
    model.addAttribute("point", 102920122);
    return "04-innerFunc";
}

4、静态化测试

之前的测试都是SpringMVC将Freemarker作为视图解析(ViewReporter)来集成到项目中,工作中,有的时候需要使用Freemarker原生Api来生成静态内容,下面一起来学习下原生Api生成文本文件。

4.1 需求分析

使用freemarker原生Api将页面生成html文件,本节测试html文件生成的方法:
在这里插入图片描述

4.2 静态化测试

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

    @Autowired
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
        //freemarker的模板对象,获取模板
        Template template = configuration.getTemplate("02-list.ftl");
        Map params = this.getData();
        //合成
        //第一个参数 数据模型
        //第二个参数  输出流
        template.process(params, new FileWriter("d:/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);

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


        //创建Map数据
        HashMap<String, Student> stuMap = new HashMap<>();
        stuMap.put("stu1", stu1);
        stuMap.put("stu2", stu2);
        //向map中存放Map数据
        map.put("stuMap", stuMap);

        //返回Map
        return map;
    }
}

对象存储服务MinIO

1、MinIO简介

MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。
MinIO兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

2、开箱使用

使用docker进行环境部署和启动

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、管理控制台

假设我们的服务器地址为http://192.168.200.130:9000,我们在地址栏输入:http://http://192.168.200.130:9000/ 即可进入登录界面。
在这里插入图片描述
Access Key为minio

Secret_key 为minio123 进入系统后可以看到主界面
在这里插入图片描述
点击右下角的“+”号 ,点击下面的图标,创建一个桶
在这里插入图片描述

4、封装MinIO为starter

文件服务在各个服务中的调用是比较多的,如果在每个服务中都去集成MinIO是很麻烦的,所以,可以把MinIO抽取为starter。

4.1 创建模块storage-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>

4.2 配置类

1、MinIOConfigProperties(配置信息)

@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;
}

2、MinIOConfig(minioClient的bean)

@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();
    }
}

4.3封装service

1、service接口

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);

}

2、接口实现类

@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();
    }
}

4.4 加入自动配置

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

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

封装为starter后,在其他服务中引入该启动依赖,自动注入service便可以使用封装好的相关功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值