2023黑马头条.微服务项目.跟学笔记(二)

2023黑马头条.微服务项目.跟学笔记 二

  • app端文章查看,静态化freemarker,分布式文件系统minIO
  • 今日简介
    • 学习内容
    • 1.文章列表加载
      • 1.1 需求分析
      • 1.2 表结构分析
        • 思考:表的垂直拆分
      • 1.3 导入文章数据库
        • 1.3.1 导入数据库
        • 1.3.2 导入对应的实体类
        • 总结
      • 1.4 实现思路
        • 1.4.1 sql练习
      • 1.5 接口定义
        • 1.5.1 响应参数
        • 1.5.2 响应结果
        • 1.5.3 步骤
        • 1.5.4 代码
      • 1.6 功能实现
        • 1.6.1 导入heima-leadnews-article微服务,资料在当天的文件夹中
        • 1.6.2 定义接口
        • 1.6.3 编写mapper文件
        • 1.6.4 编写业务层代码
        • 1.6.5 编写控制器代码
        • 1.6.6 Swagger测试或前后端联调测试
    • 2.freemarker
      • 文章详情方案选择
        • 方案1
        • 方案2 静态模板展示
      • 2.1 freemarker 介绍
        • 2.1.1 技术选型对比
      • 2.2 环境搭建&&快速入门
        • 2.2.1 创建测试工程
        • 2.2.2 配置文件
        • 2.2.3 创建模型类
        • 2.2.4 创建模板
        • 2.2.5 创建controller
        • 2.2.6 创建启动类
        • 2.2.7 测试
        • 2.2.8 更改模板文件的后缀名
      • 2.3 freemarker基础
        • 2.3.1 基础语法种类
        • 2.3.2 集合指令(List和Map)
        • 2.3.3 if指令
        • 2.3.4 运算符
        • 2.3.5 空值处理
        • 2.3.6 内建函数
      • 2.4 静态化测试
        • 2.4.1 需求分析
        • 2.4.2 静态化测试
    • 3.对象存储服务MinIO
      • 3.1 MinIO简介
      • 3.2 MinIO特点
      • 3.3 开箱使用
        • 3.3.1 安装启动
        • 3.3.2 管理控制台
      • 3.4 快速入门
        • 3.4.1 创建工程,导入pom依赖
      • 3.5 封装MinIO为starter
        • 3.5.1 创建模块heima-file-starter
        • 3.5.2 配置类
        • 3.5.3 封装操作minIO类
        • 3.5.4 对外加入自动配置
        • 3.5.5 其他微服务使用
    • 4.文章详情
      • 4.1)需求分析
      • 4.2)实现方案
        • 方案一
        • 方案二
      • 4.3)实现步骤

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

今日简介

学习内容

在这里插入图片描述
在这里插入图片描述

1.文章列表加载

1.1 需求分析

1.文章布局展示
标题、内容、图片(无图、单图、多图)
2.不同频道的切换
在这里插入图片描述

1.2 表结构分析

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

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

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

思考:表的垂直拆分

思考:
只有1张文章信息表就行了,为什么要拆分成文章配置表和文章内容表这多张表?
在这里插入图片描述

1.3 导入文章数据库

1.3.1 导入数据库

查看当天资料文件夹,在数据库连接工具中执行leadnews_article.sql

1.3.2 导入对应的实体类

ap_article文章表对应实体

package com.heima.model.article.pojos;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** <p>* 文章信息表,存储已发布的文章* </p>** @author itheima*/@Data
@TableName("ap_article")
public class ApArticle implements Serializable {@TableId(value = "id",type = IdType.ID_WORKER)private Long id;/*** 标题*/private String title;/*** 作者id*/@TableField("author_id")private Long authorId;/*** 作者名称*/@TableField("author_name")private String authorName;/*** 频道id*/@TableField("channel_id")private Integer channelId;/*** 频道名称*/@TableField("channel_name")private String channelName;/*** 文章布局  0 无图文章   1 单图文章    2 多图文章*/private Short layout;/*** 文章标记  0 普通文章   1 热点文章   2 置顶文章   3 精品文章   4 大V 文章*/private Byte flag;/*** 文章封面图片 多张逗号分隔*/private String images;/*** 标签*/private String labels;/*** 点赞数量*/private Integer likes;/*** 收藏数量*/private Integer collection;/*** 评论数量*/private Integer comment;/*** 阅读数量*/private Integer views;/*** 省市*/@TableField("province_id")private Integer provinceId;/*** 市区*/@TableField("city_id")private Integer cityId;/*** 区县*/@TableField("county_id")private Integer countyId;/*** 创建时间*/@TableField("created_time")private Date createdTime;/*** 发布时间*/@TableField("publish_time")private Date publishTime;/*** 同步状态*/@TableField("sync_status")private Boolean syncStatus;/*** 来源*/private Boolean origin;/*** 静态页面地址*/@TableField("static_url")private String staticUrl;
}

ap_article_config文章配置对应实体类

package com.heima.model.article.pojos;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;/*** <p>* APP已发布文章配置表* </p>** @author itheima*/@Data
@TableName("ap_article_config")
public class ApArticleConfig implements Serializable {@TableId(value = "id",type = IdType.ID_WORKER)private Long id;/*** 文章id*/@TableField("article_id")private Long articleId;/*** 是否可评论* true: 可以评论   1* false: 不可评论  0*/@TableField("is_comment")private Boolean isComment;/*** 是否转发* true: 可以转发   1* false: 不可转发  0*/@TableField("is_forward")private Boolean isForward;/*** 是否下架* true: 下架   1* false: 没有下架  0*/@TableField("is_down")private Boolean isDown;/*** 是否已删除* true: 删除   1* false: 没有删除  0*/@TableField("is_delete")private Boolean isDelete;
}

ap_article_content 文章内容对应的实体类

package com.heima.model.article.pojos;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;@Data
@TableName("ap_article_content")
public class ApArticleContent implements Serializable {@TableId(value = "id",type = IdType.ID_WORKER)private Long id;/*** 文章id*/@TableField("article_id")private Long articleId;/*** 文章内容*/private String content;
}

总结

在这里插入图片描述

1.4 实现思路

在这里插入图片描述

1,在默认频道展示10条文章信息(分页)

2,可以切换频道查看不同种类文章

3,当用户下拉可以加载最新的文章(分页)本页文章列表中发布时间为最大的时间为依据

4,当用户上拉可以加载更多的文章信息(按照发布时间)本页文章列表中发布时间最小的时间为依据

5,如果是当前频道的首页,前端传递默认参数:

  • maxBehotTime:0(毫秒)

  • minBehotTime:20000000000000(毫秒)—>2063年

1.4.1 sql练习

# 按照发布时间倒序查询10条文章
SELECT * FROM leadnews_article.ap_article a
ORDER BY a.`publish_time` DESC
LIMIT 0,10;# 频道筛选
SELECT DISTINCT a.`channel_name` FROM leadnews_article.ap_article a;# 只查询Java频道
SELECT * FROM leadnews_article.ap_article a
WHERE a.`channel_id` = 1
ORDER BY a.`publish_time` DESC
LIMIT 0,10;# 加载首页
SELECT * FROM leadnews_article.ap_article a
WHERE YEAR(a.`created_time`) < 2063
ORDER BY a.`publish_time` DESC
LIMIT 0,10;# 加载更多(上拉,看10条以后的消息)
SELECT * FROM leadnews_article.ap_article a
WHERE YEAR(a.`created_time`) < 2063
AND a.`channel_id` = 1
AND a.`publish_time` < '2020-09-07 22:31:19'
ORDER BY a.`publish_time` DESC
LIMIT 0,10;# 加载最新
SELECT * FROM leadnews_article.ap_article a
WHERE YEAR(a.`created_time`) < 2063
AND a.`channel_id` = 1
AND a.`publish_time` > '2020-09-07 22:31:19'
ORDER BY a.`publish_time` DESC
LIMIT 0,10;# 加载最新(关联2张表)
SELECT * 
FROM leadnews_article.ap_article a
LEFT JOIN leadnews_article.`ap_article_config` b
ON a.`id` = b.`article_id`
WHERE YEAR(a.`created_time`) < 2063
AND a.`channel_id` = 1
AND a.`publish_time` > '2020-09-07 22:31:19'
AND b.`is_down` <> 1 -- 1是已经下加
AND b.`is_delete` <> 1 -- 1是已经删除
ORDER BY a.`publish_time` DESC
LIMIT 0,10;

1.5 接口定义

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

1.5.1 响应参数

在这里插入图片描述

1.5.2 响应结果

在这里插入图片描述

1.5.3 步骤

在这里插入图片描述

1.5.4 代码

ArticleHomeDto

package com.heima.model.article.dtos;import lombok.Data;import java.util.Date;@Data
public class ArticleHomeDto {// 最大时间Date maxBehotTime;// 最小时间Date minBehotTime;// 分页sizeInteger size;// 频道IDString tag;
}

1.6 功能实现

1.6.1 导入heima-leadnews-article微服务,资料在当天的文件夹中

在这里插入图片描述

只需要拷贝相应的文件
在这里插入图片描述
然后去pom.xml文件下添加子模块
在这里插入图片描述
之后就发现导入的子模块右下角有个蓝色的小点,表示项目导入成功
在这里插入图片描述

注意:需要在heima-leadnews-service的pom文件夹中添加子模块信息,如下:

<modules><module>heima-leadnews-user</module><module>heima-leadnews-article</module>
</modules>

在idea中的maven中更新一下,如果工程还是灰色的,需要在重新添加文章微服务的pom文件,操作步骤如下:
在这里插入图片描述
需要在nacos中添加对应的配置
在这里插入图片描述
配置如下:

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

1.6.2 定义接口

package com.heima.article.controller.v1;import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {@PostMapping("/load")public ResponseResult load(@RequestBody ArticleHomeDto dto) {return null;}@PostMapping("/loadmore")public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {return null;}@PostMapping("/loadnew")public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {return null;}
}

1.6.3 编写mapper文件

package com.heima.article.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {public List<ApArticle> loadArticleList(@Param("dto") ArticleHomeDto dto, @Param("type") Short type);}

对应的映射文件

在resources中新建mapper/ApArticleMapper.xml 如下配置:

<?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">SELECTaa.*FROM`ap_article` aaLEFT JOIN ap_article_config aac ON aa.id = aac.article_id<where>and aac.is_delete != 1and 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 desclimit #{dto.size}</select></mapper>

1.6.4 编写业务层代码

package com.heima.article.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;import java.io.IOException;public interface ApArticleService extends IService<ApArticle> {/*** 根据参数加载文章列表* @param loadtype 1为加载更多  2为加载最新* @param dto* @return*/ResponseResult load(Short loadtype, ArticleHomeDto dto);}

实现类:

package com.heima.article.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Date;
import java.util.List;@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl  extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {// 单页最大加载的数字private final static short MAX_PAGE_SIZE = 50;@Autowiredprivate ApArticleMapper apArticleMapper;/*** 根据参数加载文章列表* @param loadtype 1为加载更多  2为加载最新* @param dto* @return*/@Overridepublic ResponseResult load(Short loadtype, ArticleHomeDto dto) {//1.校验参数Integer size = dto.getSize();if(size == null || size == 0){size = 10;}size = Math.min(size,MAX_PAGE_SIZE);dto.setSize(size);//类型参数检验if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;}//文章频道校验if(StringUtils.isEmpty(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> apArticles = apArticleMapper.loadArticleList(dto, loadtype);//3.结果封装ResponseResult responseResult = ResponseResult.okResult(apArticles);return responseResult;}}

定义常量类

package com.heima.common.constants;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__";}

1.6.5 编写控制器代码

package com.heima.article.controller.v1;import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {@Autowiredprivate ApArticleService apArticleService;@PostMapping("/load")public ResponseResult load(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadmore")public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);}@PostMapping("/loadnew")public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_NEW,dto);}
}

1.6.6 Swagger测试或前后端联调测试

第一:在app网关的微服务的nacos的配置中心添加文章微服务的路由,完整配置如下:
在这里插入图片描述

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

第二:启动nginx,直接使用前端项目测试,启动文章微服务,用户微服务、app网关微服务

这边启动文章微服务会碰到下面的问题,如果是找不到文件,就把target删除,然后重新编译
在这里插入图片描述
点击compile即可
在这里插入图片描述
另一个错误还是redis,只需要调整相应的配置文件即可
在这里插入图片描述

添加如下redis配置
在这里插入图片描述
启动
在这里插入图片描述

2.freemarker

文章详情方案选择

方案1

在这里插入图片描述

方案2 静态模板展示

在这里插入图片描述

2.1 freemarker 介绍

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

​ 模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

在这里插入图片描述

2.1.1 技术选型对比

常用的java模板引擎还有哪些?

Jsp、Freemarker、Thymeleaf 、Velocity 等。

1.Jsp 为 Servlet 专用,不能单独进行使用。

2.Thymeleaf 为新技术,功能较为强大,但是执行的效率比较低。

3.Velocity从2010年更新完 2.0 版本后,便没有在更新。Spring Boot 官方在 1.4 版本后对此也不在支持,虽然 Velocity 在 2017 年版本得到迭代,但为时已晚。

4.Freemarker 性能好,强大的模板语,轻量

总结:
在这里插入图片描述

2.2 环境搭建&&快速入门

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

需要创建Spring Boot+Freemarker工程用于测试模板。

2.2.1 创建测试工程

创建一个freemarker-demo 的测试工程专门用于freemarker的功能测试与模板的测试。

pom.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>heima-leadnews-test</artifactId><groupId>com.heima</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>freemarker-demo</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- apache 对 java io 的封装工具库 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version></dependency></dependencies></project>

2.2.2 配置文件

配置application.yml

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

2.2.3 创建模型类

在freemarker的测试工程下创建模型类型用于测试

package com.heima.freemarker.entity;import lombok.Data;import java.util.Date;@Data
public class Student {private String name;//姓名private int age;//年龄private Date birthday;//生日private Float money;//钱包
}

2.2.4 创建模板

在resources下创建templates,此目录为freemarker的默认模板存放目录。

在templates下创建模板文件 01-basic.ftl ,模板中的插值表达式最终会被freemarker替换成具体的数据。

在这里插入图片描述

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

2.2.5 创建controller

创建Controller类,向Map中添加name,最后返回模板文件。

package com.xuecheng.test.freemarker.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;import java.util.Map;@Controller
public class HelloController {@GetMapping("/basic")public String test(Model model) {//1.纯文本形式的参数model.addAttribute("name", "freemarker");//2.实体类相关的参数Student student = new Student();student.setName("小明");student.setAge(18);model.addAttribute("stu", student);return "01-basic";}
}

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>

2.2.6 创建启动类

package com.heima.freemarker;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class FreemarkerDemotApplication {public static void main(String[] args) {SpringApplication.run(FreemarkerDemotApplication.class,args);}
}

2.2.7 测试

请求:http://localhost:8881/basic
这里可能会有404的情况,原因是controller中的返回值不要带文件的后缀
在这里插入图片描述
登录后界面如下:
在这里插入图片描述

这里我们不禁会疑问,为啥Spring能通过这个字符串找到模板文件
原理
FreeMarkerAutoConfiguration
在这里插入图片描述
FreeMarkerProperties
在这里插入图片描述
综上所述可以定位到。

2.2.8 更改模板文件的后缀名

在这里插入图片描述
先注释掉suffix: .ftl ,这样默认会读取ftlh的后缀
在这里插入图片描述
那么我们需要重命名一下配置文件01-basic.ftlh
在这里插入图片描述
重启项目后,依然没有问题
在这里插入图片描述
同理html一样的

总结:
在这里插入图片描述

2.3 freemarker基础

2.3.1 基础语法种类

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

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

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

Hello ${name}

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

<# >FTL指令</#> 

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

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

2.3.2 集合指令(List和Map)

1、数据模型:
List集合的遍历
在这里插入图片描述
Map集合的遍历
在这里插入图片描述
在HelloController中新增如下方法:

@GetMapping("/list")
public String list(Model model){//------------------------------------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数据HashMap<String,Student> stuMap = new HashMap<>();stuMap.put("stu1",stu1);stuMap.put("stu2",stu2);// 3.1 向model中存放Map数据model.addAttribute("stuMap", stuMap);return "02-list";
}

2、模板:

在templates中新增02-list.ftl文件

<!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>
</table>
<hr><#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:<br/>
年龄:<br/>
<br/>
<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:<br/>
年龄:<br/><br/>
<a href="###">遍历map中两个学生信息:</a><br/>
<table><tr><td>序号</td><td>姓名</td><td>年龄</td><td>钱包</td> </tr>
</table>
<hr></body>
</html>

启动后,如图
在这里插入图片描述

实例代码:

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

👆上面代码解释:

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

list添加完后启动如图:
在这里插入图片描述
获取map之后的界面如图
在这里插入图片描述
遍历map之后的界面如图
在这里插入图片描述

2.3.3 if指令

在这里插入图片描述

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

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

1、数据模型:

使用list指令中测试数据模型,判断名称为小红的数据字体显示为红色。

2、模板:

<table><tr><td>姓名</td><td>年龄</td><td>钱包</td></tr><#list stus as stu><tr><td >${stu.name}</td><td>${stu.age}</td><td >${stu.mondy}</td></tr></#list></table>

实例代码:

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

3、输出:

姓名为“小红”则字体颜色显示为红色。
在这里插入图片描述

2.3.4 运算符

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:判断左边值是否小于等于右边值

= 和 == 模板代码

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Hello World!</title>
</head>
<body><b>比较运算符</b><br/><br/><dl><dt> =/== 和 != 比较:</dt><dd><#if "xiaoming" == "xiaoming">字符串的比较 "xiaoming" == "xiaoming"</#if></dd><dd><#if 10 != 100>数值的比较 10 != 100</#if></dd></dl><dl><dt>其他比较</dt><dd><#if 10 gt 5 >形式一:使用特殊字符比较数值 10 gt 5</#if></dd><dd><#-- 日期的比较需要通过?date将属性转为data类型才能进行比较 --><#if (date1?date >= date2?date)>形式二:使用括号形式比较时间 date1?date >= date2?date</#if></dd></dl><br/>
<hr>
</body>
</html>

Controller 的 数据模型代码

@GetMapping("operation")
public String testOperation(Model model) {//构建 Date 数据Date now = new Date();model.addAttribute("date1", now);model.addAttribute("date2", now);return "03-operation";
}

比较运算符注意

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

3、逻辑运算符

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

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

模板代码

<b>逻辑运算符</b><br/><br/><#if (10 lt 12 )&&( 10  gt  5 )  >(10 lt 12 )&&( 10  gt  5 )  显示为 true</#if><br/><br/><#if !false>false 取反为true</#if>
<hr>

2.3.5 空值处理

1、判断某变量是否存在使用 “??”
比如我们把list的传参注释掉
在这里插入图片描述
再看网页,会报错
在这里插入图片描述
报错信息很明显,没有空值判断
在这里插入图片描述

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

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

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

这时候再看,就可以正常显示了
代码如下:

 <#--判断集合是否有空元素--><#if stus??><#--遍历集合--><#list stus as stu><#if stu.name='小红'><tr style="color: red"><td>>${stu_index + 1}</td><td>${stu.name}</td><td>${stu.age}</td><td>${stu.money}</td></tr><#else ><tr><td>>${stu_index + 1}</td><td>${stu.name}</td><td>${stu.age}</td><td>${stu.money}</td></tr></#if></#list></#if>

在这里插入图片描述

2、缺失变量默认值使用 “!”
比如我们注释掉name的入参
在这里插入图片描述
访问网页,会报错
在这里插入图片描述
后台报错信息,需要对name判空处理
在这里插入图片描述

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

    例: ${name!‘’}表示如果name为空显示空字符串。
    更改如下:
    在这里插入图片描述

Hello ${name!''} <br>

再次登录,没有问题了
在这里插入图片描述

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

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

2.3.6 内建函数

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

1、和到某个集合的大小

${集合名?size}

在这里插入图片描述
再比如我们在02-list.ftlh中增加

stus集合的大小${stus?size}

在这里插入图片描述

2、日期格式化

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

HelloController.java新增方法

    @GetMapping("/date")public String testdate(Model model) {//------------------------------------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);//创建Map数据HashMap<String, Student> stuMap = new HashMap<>();stuMap.put("stu1", stu1);stuMap.put("stu2", stu2);// 3.1 向model中存放Map数据model.addAttribute("stuMap", stuMap);model.addAttribute("today", new Date());return "04-date";}

创建新文件04-date.ftlh

当前的日期为: ${today?datetime}

访问网页
在这里插入图片描述
自定义格式

自定义日期为: ${today?string("yyyy年MM月dd日")}

访问网页
在这里插入图片描述

3、内建函数c

model.addAttribute(“point”, 102920122);

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

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

${point?c}

HelloController.java新增方法

 @GetMapping("/c")public String testc(Model model) {// 常数值类型model.addAttribute("point", 123456789);return "05-c";}

创建文件05-c.ftlh

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Hello World!</title>
</head>
<body>
当前的内嵌函数${point}<hr>
</body>
</html>

效果如下:
在这里插入图片描述
使用内建函数

当前的内嵌函数${point?c}

在这里插入图片描述

4、将json字符串转成对象

一个例子:

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

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

模板代码:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>inner Function</title>
</head>
<body><b>获得集合大小</b><br>集合大小:<hr><b>获得日期</b><br>显示年月日:      <br>显示时分秒:<br>显示日期+时间:<br>自定义格式化:  <br><hr><b>内建函数C</b><br>没有C函数显示的数值: <br>有C函数显示的数值:<hr><b>声明变量assign</b><br><hr>
</body>
</html>

内建函数模板页面:

<!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";
}

总结:
在这里插入图片描述

2.4 静态化测试

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

2.4.1 需求分析

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

2.4.2 静态化测试

根据模板文件生成html文件

①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:
在这里插入图片描述

server: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   #模板存放位置

②:在test下创建测试类

package com.heima.freemarker.test;import com.heima.freemarker.FreemarkerDemoApplication;
import com.heima.freemarker.entity.Student;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
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.FileWriter;
import java.io.IOException;
import java.util.*;@SpringBootTest(classes = FreemarkerDemoApplication.class)
@RunWith(SpringRunner.class)
public class FreemarkerTest {@Autowiredprivate Configuration configuration;@Testpublic void test() throws IOException, TemplateException {//freemarker的模板对象,获取模板Template template = configuration.getTemplate("02-list.ftl");Map params = getData();//合成//第一个参数 数据模型//第二个参数  输出流template.process(params, new FileWriter("F:\javawebwork\heima-leadnews-html\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);//返回Mapreturn map;}
}

执行测试类后在文件路径中,多出了html文件
在这里插入图片描述

3.对象存储服务MinIO

3.1 MinIO简介

MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。

MinIO兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

S3 ( Simple Storage Service简单存储服务)

基本概念

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

官网文档:http://docs.minio.org.cn/docs/

3.2 MinIO特点

  • 数据保护

    Minio使用Minio Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的driver,但是仍然可以从中恢复。

  • 高性能

    作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率

  • 可扩容

    不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心

  • SDK支持

    基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持

  • 有操作页面

    面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源

  • 功能简单

    这一设计原则让MinIO不容易出错、更快启动

  • 丰富的API

    支持文件资源的分享连接及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能等。

  • 文件变化主动通知

    存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。

在这里插入图片描述

在这里插入图片描述

3.3 开箱使用

3.3.1 安装启动

我们提供的镜像中已经有minio的环境

我们可以使用docker进行环境部署和启动

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

3.3.2 管理控制台

假设我们的服务器地址为http://192.168.200.130:9000,我们在地址栏输入:http://192.168.200.130:9000/ 即可进入登录界面。

在这里插入图片描述

Access Key为minioadmin Secret_key 为minioadmin 进入系统后可以看到主界面
在这里插入图片描述

点击右下角的“+”号 ,点击下面的图标,创建一个桶
在这里插入图片描述
我们创建一个桶名字为leadnews
在这里插入图片描述

3.4 快速入门

3.4.1 创建工程,导入pom依赖

创建minio-demo,对应pom如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>heima-leadnews-test</artifactId><groupId>com.heima</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>minio-demo</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>7.1.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies></project>

引导类:

package com.heima.minio;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class MinIOApplication {public static void main(String[] args) {SpringApplication.run(MinIOApplication.class,args);}
}

创建测试类,上传html文件

package com.heima.minio.test;import io.minio.MinioClient;
import io.minio.PutObjectArgs;import java.io.FileInputStream;public class MinIOTest {public static void main(String[] args) {FileInputStream fileInputStream = null;try {fileInputStream =  new FileInputStream("D:\\list.html");;//1.创建minio链接客户端MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.200.130:9000").build();//2.上传PutObjectArgs putObjectArgs = PutObjectArgs.builder().object("list.html")//文件名.contentType("text/html")//文件类型.bucket("leadnews")//桶名词  与minio创建的名词一致.stream(fileInputStream, fileInputStream.available(), -1) //文件流.build();minioClient.putObject(putObjectArgs);System.out.println("http://192.168.200.130:9000/leadnews/list.html");} catch (Exception ex) {ex.printStackTrace();}}}

运行完之后,文件上传成功了
在这里插入图片描述
但是现在文件查看不了,也没办法修改,需要我们修改权限,步骤如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这个时候,就可以访问控制台输出的路径了
在这里插入图片描述

3.5 封装MinIO为starter

在这里插入图片描述
具体步骤如下:
在这里插入图片描述

在这里插入图片描述
在父工程的pom.xml文件中添加

<module>heima-leadnews-basic</module>

在这里插入图片描述
添加完后,文件右下角出现蓝点
在这里插入图片描述

3.5.1 创建模块heima-file-starter

在minio-demo模块中导入依赖
在这里插入图片描述

<dependency><groupId>com.heima</groupId><artifactId>heima-file-starter</artifactId><version>1.0-SNAPSHOT</version></dependency>

导入依赖

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

3.5.2 配置类

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 {@Autowiredprivate MinIOConfigProperties minIOConfigProperties;@Beanpublic MinioClient buildMinioClient(){return MinioClient.builder().credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey()).endpoint(minIOConfigProperties.getEndpoint()).build();}
}

3.5.3 封装操作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 {@Autowiredprivate MinioClient minioClient;@Autowiredprivate 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  文件全路径*/@Overridepublic 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  文件全路径*/@Overridepublic 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  文件全路径*/@Overridepublic 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);// 删除ObjectsRemoveObjectArgs 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  文件流**/@Overridepublic 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();}
}

3.5.4 对外加入自动配置

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

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

3.5.5 其他微服务使用

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

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

在minio-demo模块下创建配置文件application.yml

minio:accessKey: minioadminsecretKey: minioadminbucket: leadnewsendpoint: http://192.168.200.130:9000readPath: 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 {@Autowiredprivate FileStorageService fileStorageService;@Testpublic 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();}}
}

注入后,运行测试类
在这里插入图片描述

4.文章详情

4.1)需求分析

在这里插入图片描述

4.2)实现方案

方案一

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

方案二

在这里插入图片描述

4.3)实现步骤

1.在artile微服务中添加MinIO和freemarker的支持,参考测试项目
直接把freemarker-demo和minio-demo模块相关的依赖和文件拷贝去heima-leadnews-article即可
在这里插入图片描述

2.资料中找到模板文件(article.ftl)拷贝到article微服务下
在这里插入图片描述
在这里插入图片描述

3.资料中找到index.js和index.css两个文件手动上传到MinIO中
在这里插入图片描述

上传文件
修改MinIOTest.java,可以新建一个测试类

@Testpublic void testCss() {FileInputStream fileInputStream = null;try {fileInputStream = new FileInputStream("E:\\黑马\\黑马头条\\day02-app端文章查看,静态化freemarker,分布式文件系统minIO\\资料\\模板文件\\plugins\\css\\index.css");//1.创建minio链接客户端MinioClient client = MinioClient.builder().credentials("minioadmin", "minioadmin").endpoint("http://192.168.150.1:9000/").build();//2.上传PutObjectArgs putObjectArgs = PutObjectArgs.builder().object("plugins/css/index.css") // 文件名称.contentType("css") // 文件类型.bucket("leadnews") // 桶名称与minio创建的桶名称一致.stream(fileInputStream, fileInputStream.available(), -1) // -1指的是把所有内容都传进去.build();client.putObject(putObjectArgs);// 拼接一下访问路径System.out.println("http://192.168.150.1:9000/leadnews/plugins/css/index.css");} catch (Exception ex) {ex.printStackTrace();} finally {try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}

运行后上传成功
在这里插入图片描述
js文件的上传同理即可。

4.在文章微服务中导入依赖

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

5.新建ApArticleContentMapper

package com.heima.article.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.pojos.ApArticleContent;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface ApArticleContentMapper extends BaseMapper<ApArticleContent> {
}

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

package com.heima.article.test;import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.ArticleApplication;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleContent;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang3.StringUtils;
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.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;@SpringBootTest(classes = ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {@Autowiredprivate Configuration configuration;@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate ApArticleMapper apArticleMapper;@Autowiredprivate ApArticleContentMapper apArticleContentMapper;@Testpublic 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文件StringWriter out = new StringWriter();Template template = configuration.getTemplate("article.ftl");Map<String, Object> params = new HashMap<>();params.put("content", JSONArray.parseArray(apArticleContent.getContent()));template.process(params, out);InputStream is = new ByteArrayInputStream(out.toString().getBytes());//3.把html文件上传到minio中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);}}
}

运行之前static_url为空
在这里插入图片描述
运行之后static_url有字段填充,是一个url地址,我们访问一下,可以看到相关字段
在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

界面控件Telerik UI for WPF R2 2023——拥有全新的Windows 11精简主题

Telerik UI for WPF拥有超过100个控件来创建美观、高性能的桌面应用程序&#xff0c;同时还能快速构建企业级办公WPF应用程序。Telerik UI for WPF支持MVVM、触摸等&#xff0c;创建的应用程序可靠且结构良好&#xff0c;非常容易维护&#xff0c;其直观的API将无缝地集成Visua…

idea自定义类注释以及方法注释,无警告

背景 idea&#xff1a;IntelliJ IDEA 2023.1.3 (Ultimate Edition) 效果 类 方法 正式&#xff1a;类 设置 代码 /** * author: 你的名字* date: ${DATE} on ${TIME}* desc: $NAME*/注意&#xff0c;请全部复制&#xff0c;空行也要&#xff0c;看设置截图选中部分 …

使用Megascans,Blender和Substance 3D画家创建渔人旅馆(p2)

今天云渲染小编接着Polina Tarakanova分享的Fishermans Inn项目上篇分享&#xff0c;下篇主要是纹理和材料、组装场景、照明等方面的分享。 纹理和材料 随着酒馆的模块化建设完成&#xff0c;是时候进入贴图阶段了。我使用Substance 3D Painter进行了所有的贴图工作。在我的场…

python实现削苹果小游戏

也不用998只有199源码发你。 支付完发我邮箱发你源代码。

elment-ui的Cascader 级联选择器,点击lable 也能选中前面的复选框

直接mounted里加就OK啦 mounted() {// Cascader 级联选择器: 点击文本就让它自动点击前面的input就可以触发选择。setInterval(function() {document.querySelectorAll(.el-cascader-node__label).forEach(el > {el.onclick function() {if (this.previousElementSibl…

计算机视觉:3*3卷积核的优势

本文重点 如果你了解卷积神经网络,那么你一定会注意到大多数卷积神经网络模型经常使用3*3的卷积核,甚至是1*1的,而5*5的都少用,这是为什么呢?本文对3*3的卷积神经网络的好处进行总结。 参数量少 在卷积神经网络中,卷积核的大小决定了模型的参数量。3*3的卷积核比5*5…

jenkins邮箱设置报:501 mail from address must be same as authorization user

jenkins配置邮箱时遇到如下错误&#xff1a;501 mail from address must be same as authorization user 原因是管理员邮箱地址与发送邮箱地址不统一&#xff0c;配置管理员邮件地址&#xff1a;系统管理-系统配置-Jenkins Location&#xff0c;输入与发件人统一的地址即可

【状态估计】粒子滤波器、Σ点滤波器和扩展/线性卡尔曼滤波器研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【STM32】基于stm32的阿里云智能家居

摘 要 智能家居是一种通过物联网将家里的各种电器设备连接在一起&#xff0c;并由中心控制器统一管理的信息系统。系统的核心是各类家居信息的采集与处理。阿里云能够提供云端的数据存储和分析功能&#xff0c;可以作为智能家居中心控制器的重要平台。 本文主要研究了基于阿里云…

时间触发嵌入式系统:各种系统的概念

1.1 引言 一说到软件系统&#xff0c;脑子里面就会闪现很多名词&#xff1a; 信息系统 桌面应用系统 实时系统 嵌入式系统 事件触发系统 时间触发系统 这些名字搞得脑子很乱&#xff0c;感觉都熟悉&#xff0c;又有些陌生&#xff0c;还是需要简单的介绍一下。 1.2 信息系统…

走进人工智能| Computer Vision 数字化时代的视觉启示录

前言&#xff1a; 计算机视觉是通过模仿人类视觉系统的工作原理&#xff0c;使计算机能够感知、理解和解释图像和视频的能力。 文章目录 序言背景适用领域技术支持应用领域程序员如何学总结 序言 计算机视觉是人工智能领域的一个重要分支&#xff0c;它涉及使计算机能够“看”…

TCP的socket API

1、核心类 ServerSocket &#xff1a;服务器使用的socket Socket : 服务器和客户端都会使用的socket accept进行的工作是拉客 对应操作系统来说&#xff0c;建立TCP连接是内核的工作 accept要干的就是等连接建立好了&#xff0c;把这个连接给拿到应用程序中。 如果当前连接…