app端文章查看,静态化freemarker,分布式文件系统minIO
1)文章列表加载
1.1)需求分析
文章布局展示
1.2)表结构分析
ap_article 文章基本信息表
ap_article_config 文章配置表
ap_article_content 文章内容表
三张表关系分析
1.3)导入文章数据库
1.3.1)导入数据库
查看当天资料文件夹,在数据库连接工具中执行leadnews_article.sql
1.3.2)导入对应的实体类
ap_article文章表对应实体
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">package</span> <span style="color:#0000ff">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">article</span>.<span style="color:#000000">pojos</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">IdType</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableField</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableId</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableName</span>;
<span style="color:#770088">import</span> <span style="color:#000000">lombok</span>.<span style="color:#000000">Data</span>;
<span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">io</span>.<span style="color:#000000">Serializable</span>;
<span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">util</span>.<span style="color:#000000">Date</span>;
<span style="color:#aa5500">/**</span>
 <span style="color:#aa5500">* <p></span>
 <span style="color:#aa5500">* 文章信息表,存储已发布的文章</span>
 <span style="color:#aa5500">* </p></span>
 <span style="color:#aa5500">*</span>
 <span style="color:#aa5500">* @author itheima</span>
 <span style="color:#aa5500">*/</span>
<span style="color:#555555">@Data</span>
<span style="color:#555555">@TableName</span>(<span style="color:#aa1111">"ap_article"</span>)
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">ApArticle</span> <span style="color:#770088">implements</span> <span style="color:#000000">Serializable</span> {
    <span style="color:#555555">@TableId</span>(<span style="color:#000000">value</span> <span style="color:#981a1a">=</span> <span style="color:#aa1111">"id"</span>,<span style="color:#000000">type</span> <span style="color:#981a1a">=</span> <span style="color:#000000">IdType</span>.<span style="color:#000000">ID_WORKER</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Long</span> <span style="color:#000000">id</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 标题</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">title</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 作者id</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"author_id"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Long</span> <span style="color:#000000">authorId</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 作者名称</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"author_name"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">authorName</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 频道id</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"channel_id"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">channelId</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 频道名称</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"channel_name"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">channelName</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 文章布局  0 无图文章   1 单图文章    2 多图文章</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">Short</span> <span style="color:#000000">layout</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 文章标记  0 普通文章   1 热点文章   2 置顶文章   3 精品文章   4 大V 文章</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">Byte</span> <span style="color:#000000">flag</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 文章封面图片 多张逗号分隔</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">images</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 标签</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">labels</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 点赞数量</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">likes</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 收藏数量</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">collection</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 评论数量</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">comment</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 阅读数量</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">views</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 省市</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"province_id"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">provinceId</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 市区</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"city_id"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">cityId</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 区县</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"county_id"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Integer</span> <span style="color:#000000">countyId</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 创建时间</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"created_time"</span>)
    <span style="color:#770088">private</span> <span style="color:#000000">Date</span> <span style="color:#000000">createdTime</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 发布时间</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"publish_time"</span>)
    <span style="color:#770088">private</span> <span style="color:#000000">Date</span> <span style="color:#000000">publishTime</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 同步状态</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"sync_status"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Boolean</span> <span style="color:#000000">syncStatus</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 来源</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">Boolean</span> <span style="color:#000000">origin</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 静态页面地址</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"static_url"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">staticUrl</span>;
}</span></span> 
ap_article_config文章配置对应实体类
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">package</span> <span style="color:#0000ff">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">article</span>.<span style="color:#000000">pojos</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">IdType</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableField</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableId</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableName</span>;
<span style="color:#770088">import</span> <span style="color:#000000">lombok</span>.<span style="color:#000000">Data</span>;
<span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">io</span>.<span style="color:#000000">Serializable</span>;
<span style="color:#aa5500">/**</span>
 <span style="color:#aa5500">* <p></span>
 <span style="color:#aa5500">* APP已发布文章配置表</span>
 <span style="color:#aa5500">* </p></span>
 <span style="color:#aa5500">*</span>
 <span style="color:#aa5500">* @author itheima</span>
 <span style="color:#aa5500">*/</span>
<span style="color:#555555">@Data</span>
<span style="color:#555555">@TableName</span>(<span style="color:#aa1111">"ap_article_config"</span>)
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">ApArticleConfig</span> <span style="color:#770088">implements</span> <span style="color:#000000">Serializable</span> {
    <span style="color:#555555">@TableId</span>(<span style="color:#000000">value</span> <span style="color:#981a1a">=</span> <span style="color:#aa1111">"id"</span>,<span style="color:#000000">type</span> <span style="color:#981a1a">=</span> <span style="color:#000000">IdType</span>.<span style="color:#000000">ID_WORKER</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Long</span> <span style="color:#000000">id</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 文章id</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"article_id"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Long</span> <span style="color:#000000">articleId</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 是否可评论</span>
     <span style="color:#aa5500">* true: 可以评论   1</span>
     <span style="color:#aa5500">* false: 不可评论  0</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"is_comment"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Boolean</span> <span style="color:#000000">isComment</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 是否转发</span>
     <span style="color:#aa5500">* true: 可以转发   1</span>
     <span style="color:#aa5500">* false: 不可转发  0</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"is_forward"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Boolean</span> <span style="color:#000000">isForward</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 是否下架</span>
     <span style="color:#aa5500">* true: 下架   1</span>
     <span style="color:#aa5500">* false: 没有下架  0</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"is_down"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Boolean</span> <span style="color:#000000">isDown</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 是否已删除</span>
     <span style="color:#aa5500">* true: 删除   1</span>
     <span style="color:#aa5500">* false: 没有删除  0</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"is_delete"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Boolean</span> <span style="color:#000000">isDelete</span>;
}</span></span> 
ap_article_content 文章内容对应的实体类
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">package</span> <span style="color:#0000ff">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">article</span>.<span style="color:#000000">pojos</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">IdType</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableField</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableId</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">TableName</span>;
<span style="color:#770088">import</span> <span style="color:#000000">lombok</span>.<span style="color:#000000">Data</span>;
<span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">io</span>.<span style="color:#000000">Serializable</span>;
<span style="color:#555555">@Data</span>
<span style="color:#555555">@TableName</span>(<span style="color:#aa1111">"ap_article_content"</span>)
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">ApArticleContent</span> <span style="color:#770088">implements</span> <span style="color:#000000">Serializable</span> {
    <span style="color:#555555">@TableId</span>(<span style="color:#000000">value</span> <span style="color:#981a1a">=</span> <span style="color:#aa1111">"id"</span>,<span style="color:#000000">type</span> <span style="color:#981a1a">=</span> <span style="color:#000000">IdType</span>.<span style="color:#000000">ID_WORKER</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Long</span> <span style="color:#000000">id</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 文章id</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#555555">@TableField</span>(<span style="color:#aa1111">"article_id"</span>)
    <span style="color:#770088">private</span> <span style="color:#008855">Long</span> <span style="color:#000000">articleId</span>;
    <span style="color:#aa5500">/**</span>
     <span style="color:#aa5500">* 文章内容</span>
     <span style="color:#aa5500">*/</span>
    <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">content</span>;
}</span></span> 
1.4)实现思路
1,在默认频道展示10条文章信息
2,可以切换频道查看不同种类文章
3,当用户下拉可以加载最新的文章(分页)本页文章列表中发布时间为最大的时间为依据
4,当用户上拉可以加载更多的文章信息(按照发布时间)本页文章列表中发布时间最小的时间为依据
5,如果是当前频道的首页,前端传递默认参数:
-  
maxBehotTime:0(毫秒)
 -  
minBehotTime:20000000000000(毫秒)--->2063年
 
1.5)接口定义
| 加载首页 | 加载更多 | 加载最新 | |
|---|---|---|---|
| 接口路径 | /api/v1/article/load | /api/v1/article/loadmore | /api/v1/article/loadnew | 
| 请求方式 | POST | POST | POST | 
| 参数 | ArticleHomeDto | ArticleHomeDto | ArticleHomeDto | 
| 响应结果 | ResponseResult | ResponseResult | ResponseResult | 
ArticleHomeDto
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">package</span> <span style="color:#0000ff">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">article</span>.<span style="color:#000000">dtos</span>;
<span style="color:#770088">import</span> <span style="color:#000000">lombok</span>.<span style="color:#000000">Data</span>;
<span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">util</span>.<span style="color:#000000">Date</span>;
<span style="color:#555555">@Data</span>
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">ArticleHomeDto</span> {
    <span style="color:#aa5500">// 最大时间</span>
    <span style="color:#000000">Date</span> <span style="color:#000000">maxBehotTime</span>;
    <span style="color:#aa5500">// 最小时间</span>
    <span style="color:#000000">Date</span> <span style="color:#000000">minBehotTime</span>;
    <span style="color:#aa5500">// 分页size</span>
    <span style="color:#008855">Integer</span> <span style="color:#000000">size</span>;
    <span style="color:#aa5500">// 频道ID</span>
    <span style="color:#008855">String</span> <span style="color:#000000">tag</span>;
}</span></span> 
1.6)功能实现
1.6.1):导入heima-leadnews-article微服务,资料在当天的文件夹中
注意:需要在heima-leadnews-service的pom文件夹中添加子模块信息,如下:
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#117700"><</span><span style="color:#117700">modules</span><span style="color:#117700">></span>
    <span style="color:#117700"><</span><span style="color:#117700">module</span><span style="color:#117700">></span>heima-leadnews-user<span style="color:#117700"></</span><span style="color:#117700">module</span><span style="color:#117700">></span>
    <span style="color:#117700"><</span><span style="color:#117700">module</span><span style="color:#117700">></span>heima-leadnews-article<span style="color:#117700"></</span><span style="color:#117700">module</span><span style="color:#117700">></span>
<span style="color:#117700"></</span><span style="color:#117700">modules</span><span style="color:#117700">></span></span></span> 
在idea中的maven中更新一下,如果工程还是灰色的,需要在重新添加文章微服务的pom文件,操作步骤如下:
需要在nacos中添加对应的配置
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#221199">spring</span><span style="color:#555555">:</span>
<span style="color:#221199">  datasource</span><span style="color:#555555">:</span>
<span style="color:#221199">    driver-class-name</span><span style="color:#555555">: </span>com.mysql.jdbc.Driver
<span style="color:#221199">    url</span><span style="color:#555555">: </span>jdbc<span style="color:#555555">:</span>mysql<span style="color:#555555">:</span>//localhost<span style="color:#555555">:</span>3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
<span style="color:#221199">    username</span><span style="color:#555555">: </span>root
<span style="color:#221199">    password</span><span style="color:#555555">: </span>root
<span style="color:#aa5500"># 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置</span>
<span style="color:#221199">mybatis-plus</span><span style="color:#555555">:</span>
<span style="color:#221199">  mapper-locations</span><span style="color:#555555">: </span>classpath*<span style="color:#555555">:</span>mapper/*.xml
  <span style="color:#aa5500"># 设置别名包扫描路径,通过该属性可以给包中的类注册别名</span>
<span style="color:#221199">  type-aliases-package</span><span style="color:#555555">: </span>com.heima.model.article.pojos</span></span> 
1.6.2):定义接口
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">package</span> <span style="color:#0000ff">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">article</span>.<span style="color:#000000">controller</span>.<span style="color:#000000">v1</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">article</span>.<span style="color:#000000">dtos</span>.<span style="color:#000000">ArticleHomeDto</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">common</span>.<span style="color:#000000">dtos</span>.<span style="color:#000000">ResponseResult</span>;
<span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">web</span>.<span style="color:#000000">bind</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">PostMapping</span>;
<span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">web</span>.<span style="color:#000000">bind</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">RequestBody</span>;
<span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">web</span>.<span style="color:#000000">bind</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">RequestMapping</span>;
<span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">web</span>.<span style="color:#000000">bind</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">RestController</span>;
<span style="color:#555555">@RestController</span>
<span style="color:#555555">@RequestMapping</span>(<span style="color:#aa1111">"/api/v1/article"</span>)
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">ArticleHomeController</span> {
    <span style="color:#555555">@PostMapping</span>(<span style="color:#aa1111">"/load"</span>)
    <span style="color:#770088">public</span> <span style="color:#000000">ResponseResult</span> <span style="color:#000000">load</span>(<span style="color:#555555">@RequestBody</span> <span style="color:#000000">ArticleHomeDto</span> <span style="color:#000000">dto</span>) {
        <span style="color:#770088">return</span> <span style="color:#221199">null</span>;
    }
    <span style="color:#555555">@PostMapping</span>(<span style="color:#aa1111">"/loadmore"</span>)
    <span style="color:#770088">public</span> <span style="color:#000000">ResponseResult</span> <span style="color:#000000">loadMore</span>(<span style="color:#555555">@RequestBody</span> <span style="color:#000000">ArticleHomeDto</span> <span style="color:#000000">dto</span>) {
        <span style="color:#770088">return</span> <span style="color:#221199">null</span>;
    }
    <span style="color:#555555">@PostMapping</span>(<span style="color:#aa1111">"/loadnew"</span>)
    <span style="color:#770088">public</span> <span style="color:#000000">ResponseResult</span> <span style="color:#000000">loadNew</span>(<span style="color:#555555">@RequestBody</span> <span style="color:#000000">ArticleHomeDto</span> <span style="color:#000000">dto</span>) {
        <span style="color:#770088">return</span> <span style="color:#221199">null</span>;
    }
}</span></span> 
1.6.3):编写mapper文件
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">package</span> <span style="color:#0000ff">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">article</span>.<span style="color:#000000">mapper</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">baomidou</span>.<span style="color:#000000">mybatisplus</span>.<span style="color:#000000">core</span>.<span style="color:#000000">mapper</span>.<span style="color:#000000">BaseMapper</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">article</span>.<span style="color:#000000">dtos</span>.<span style="color:#000000">ArticleHomeDto</span>;
<span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">heima</span>.<span style="color:#000000">model</span>.<span style="color:#000000">article</span>.<span style="color:#000000">pojos</span>.<span style="color:#000000">ApArticle</span>;
<span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">apache</span>.<span style="color:#000000">ibatis</span>.<span style="color:#000000">annotations</span>.<span style="color:#000000">Mapper</span>;
<span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">apache</span>.<span style="color:#000000">ibatis</span>.<span style="color:#000000">annotations</span>.<span style="color:#000000">Param</span>;
<span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">util</span>.<span style="color:#000000">List</span>;
<span style="color:#555555">@Mapper</span>
<span style="color:#770088">public</span> <span style="color:#770088">interface</span> <span style="color:#0000ff">ApArticleMapper</span> <span style="color:#770088">extends</span> <span style="color:#000000">BaseMapper</span><span style="color:#981a1a"><</span><span style="color:#000000">ApArticle</span><span style="color:#981a1a">></span> {
    <span style="color:#770088">public</span> <span style="color:#000000">List</span><span style="color:#981a1a"><</span><span style="color:#000000">ApArticle</span><span style="color:#981a1a">></span> <span style="color:#000000">loadArticleList</span>(<span style="color:#555555">@Param</span>(<span style="color:#aa1111">"dto"</span>) <span style="color:#000000">ArticleHomeDto</span> <span style="color:#000000">dto</span>, <span style="color:#555555">@Param</span>(<span style="color:#aa1111">"type"</span>) <span style="color:#008855">Short</span> <span style="color:#000000">type</span>);
}</span></span> 
对应的映射文件
在resources中新建mapper/ApArticleMapper.xml 如下配置:
<span style="background-color:#f8f8f8"><span style="color:#333333"><?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></span></span> 
1.6.4):编写业务层代码
<span style="background-color:#f8f8f8"><span style="color:#333333">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);
}</span></span> 
实现类:
<span style="background-color:#f8f8f8"><span style="color:#333333">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;
    @Autowired
    private ApArticleMapper apArticleMapper;
    /**
     * 根据参数加载文章列表
     * @param loadtype 1为加载更多  2为加载最新
     * @param dto
     * @return
     */
    @Override
    public 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;
    }
    
}</span></span> 
定义常量类
<span style="background-color:#f8f8f8"><span style="color:#333333">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__";
}</span></span> 
1.6.5):编写控制器代码
<span style="background-color:#f8f8f8"><span style="color:#333333">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 {
    @Autowired
    private 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);
    }
}</span></span> 
1.6.6):swagger测试或前后端联调测试
第一:在app网关的微服务的nacos的配置中心添加文章微服务的路由,完整配置如下:
<span style="background-color:#f8f8f8"><span style="color:#333333">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</span></span> 
第二:启动nginx,直接使用前端项目测试,启动文章微服务,用户微服务、app网关微服务
2)freemarker
2.1) freemarker 介绍
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
常用的java模板引擎还有哪些?
Jsp、Freemarker、Thymeleaf 、Velocity 等。
1.Jsp 为 Servlet 专用,不能单独进行使用。
2.Thymeleaf 为新技术,功能较为强大,但是执行的效率比较低。
3.Velocity从2010年更新完 2.0 版本后,便没有在更新。Spring Boot 官方在 1.4 版本后对此也不在支持,虽然 Velocity 在 2017 年版本得到迭代,但为时已晚。
2.2) 环境搭建&&快速入门
freemarker作为springmvc一种视图格式,默认情况下SpringMVC支持freemarker视图格式。
需要创建Spring Boot+Freemarker工程用于测试模板。
2.2.1) 创建测试工程
创建一个freemarker-demo 的测试工程专门用于freemarker的功能测试与模板的测试。
pom.xml如下
<span style="background-color:#f8f8f8"><span style="color:#333333"><?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></span></span> 
2.2.2) 配置文件
配置application.yml
<span style="background-color:#f8f8f8"><span style="color:#333333">server:
  port: 8881 #服务端口
spring:
  application:
    name: freemarker-demo #指定服务名
  freemarker:
    cache: false  #关闭模板缓存,方便测试
    settings:
      template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl               #指定Freemarker模板文件的后缀名</span></span> 
 
2.2.3) 创建模型类
在freemarker的测试工程下创建模型类型用于测试
<span style="background-color:#f8f8f8"><span style="color:#333333">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;//钱包
}</span></span> 
 
 
2.2.4) 创建模板
在resources下创建templates,此目录为freemarker的默认模板存放目录。
在templates下创建模板文件 01-basic.ftl ,模板中的插值表达式最终会被freemarker替换成具体的数据。
<span style="background-color:#f8f8f8"><span style="color:#333333"><!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></span></span> 
 
2.2.5) 创建controller
创建Controller类,向Map中添加name,最后返回模板文件。
<span style="background-color:#f8f8f8"><span style="color:#333333">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";
    }
}</span></span> 
01-basic.ftl,使用插值表达式填充数据
<span style="background-color:#f8f8f8"><span style="color:#333333"><!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></span></span> 
 
2.2.6) 创建启动类
<span style="background-color:#f8f8f8"><span style="color:#333333">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);
    }
}</span></span> 
2.2.7) 测试
请求:http://localhost:8881/basic
2.3) freemarker基础
2.3.1) 基础语法种类
1、注释,即<#-- -->,介于其之间的内容会被freemarker忽略
<span style="background-color:#f8f8f8"><span style="color:#333333"><#--我是一个freemarker注释--></span></span> 
2、插值(Interpolation):即 ${..} 部分,freemarker会用真实的值代替${..}
<span style="background-color:#f8f8f8"><span style="color:#333333">Hello ${name}</span></span> 
3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。
<span style="background-color:#f8f8f8"><span style="color:#333333"><# >FTL指令</#> </span></span> 
4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容。
<span style="background-color:#f8f8f8"><span style="color:#333333"><#--freemarker中的普通文本-->
我是一个普通的文本</span></span> 
2.3.2) 集合指令(List和Map)
1、数据模型:
在HelloController中新增如下方法:
<span style="background-color:#f8f8f8"><span style="color:#333333">@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";
}</span></span> 
2、模板:
在templates中新增02-list.ftl文件
<span style="background-color:#f8f8f8"><span style="color:#333333"><!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></span></span> 
实例代码:
<span style="background-color:#f8f8f8"><span style="color:#333333"><!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></span></span> 
👆上面代码解释:
${k_index}: index:得到循环的下标,使用方法是在stu后边加"_index",它的值是从0开始
2.3.3) if指令
if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不再输出。
-  
指令格式
 
<span style="background-color:#f8f8f8"><span style="color:#333333"><#if ></if></span></span> 
 
1、数据模型:
使用list指令中测试数据模型,判断名称为小红的数据字体显示为红色。
2、模板:
<span style="background-color:#f8f8f8"><span style="color:#333333"><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></span></span> 
 
实例代码:
<span style="background-color:#f8f8f8"><span style="color:#333333"><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></span></span> 
 
 
3、输出:
姓名为“小强”则字体颜色显示为红色。
2.3.4) 运算符
1、算数运算符
FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:
-  
加法:
+ -  
减法:
- -  
乘法:
* -  
除法:
/ -  
求模 (求余):
% 
模板代码
<span style="background-color:#f8f8f8"><span style="color:#333333"><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></span></span> 
除了 + 运算以外,其他的运算只能和 number 数字类型的计算。
2、比较运算符
-  
=或者==:判断两个值是否相等. -  
!=:判断两个值是否不等. -  
>或者gt:判断左边值是否大于右边值 -  
>=或者gte:判断左边值是否大于等于右边值 -  
<或者lt:判断左边值是否小于右边值 -  
<=或者lte:判断左边值是否小于等于右边值 
= 和 == 模板代码
<span style="background-color:#f8f8f8"><span style="color:#333333"><!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></span></span> 
Controller 的 数据模型代码
<span style="background-color:#f8f8f8"><span style="color:#333333">@GetMapping("operation")
public String testOperation(Model model) {
    //构建 Date 数据
    Date now = new Date();
    model.addAttribute("date1", now);
    model.addAttribute("date2", now);
    
    return "03-operation";
}</span></span> 
 
比较运算符注意
-  
=和!=可以用于字符串、数值和日期来比较是否相等 -  
=和!=两边必须是相同类型的值,否则会产生错误 -  
字符串
"x"、"x "、"X"比较是不等的.因为FreeMarker是精确比较 -  
其它的运行符可以作用于数字和日期,但不能作用于字符串
 -  
使用
gt等字母运算符代替>会有更好的效果,因为 FreeMarker会把>解释成FTL标签的结束字符 -  
可以使用括号来避免这种情况,如:
<#if (x>y)> 
3、逻辑运算符
-  
逻辑与:&&
 -  
逻辑或:||
 -  
逻辑非:!
 
逻辑运算符只能作用于布尔值,否则将产生错误 。
模板代码
<span style="background-color:#f8f8f8"><span style="color:#333333"><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></span></span> 
 
2.3.5) 空值处理
1、判断某变量是否存在使用 “??”
用法为:variable??,如果该变量存在,返回true,否则返回false
例:为防止stus为空报错可以加上判断如下:
<span style="background-color:#f8f8f8"><span style="color:#333333">    <#if stus??>
    <#list stus as stu>
    	......
    </#list>
    </#if></span></span> 
 
2、缺失变量默认值使用 “!”
-  
使用!要以指定一个默认值,当变量为空时显示默认值
例: ${name!''}表示如果name为空显示空字符串。
 
-  
如果是嵌套对象则建议使用()括起来
例: ${(stu.bestFriend.name)!''}表示,如果stu或bestFriend或name为空默认显示空字符串。
 
2.3.6) 内建函数
内建函数语法格式: 变量+?+函数名称 
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标签,assign的作用是定义一个变量。
<span style="background-color:#f8f8f8"><span style="color:#333333"><#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
<#assign data=text?eval />
开户行:${data.bank}  账号:${data.account}</span></span> 
 
模板代码:
<span style="background-color:#f8f8f8"><span style="color:#333333"><!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></span></span> 
 
 
内建函数模板页面:
<span style="background-color:#f8f8f8"><span style="color:#333333"><!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></span></span> 
内建函数Controller数据模型:
<span style="background-color:#f8f8f8"><span style="color:#333333">@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";
}</span></span> 
 
 
 
2.4) 静态化测试
之前的测试都是SpringMVC将Freemarker作为视图解析器(ViewReporter)来集成到项目中,工作中,有的时候需要使用Freemarker原生Api来生成静态内容,下面一起来学习下原生Api生成文本文件。
2.4.1) 需求分析
使用freemarker原生Api将页面生成html文件,本节测试html文件生成的方法:
2.4.2) 静态化测试
根据模板文件生成html文件
①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:
<span style="background-color:#f8f8f8"><span style="color:#333333">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   #模板存放位置</span></span> 
②:在test下创建测试类
<span style="background-color:#f8f8f8"><span style="color:#333333">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 {
    @Autowired
    private Configuration configuration;
    @Test
    public void test() throws IOException, TemplateException {
        //freemarker的模板对象,获取模板
        Template template = configuration.getTemplate("02-list.ftl");
        Map params = 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;
    }
}</span></span> 
 
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 – 类比文件名
 
官网文档:MinIO Quickstart Guide| Minio中文文档
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进行环境部署和启动
<span style="background-color:#f8f8f8"><span style="color:#333333">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</span></span> 
3.3.2 管理控制台
假设我们的服务器地址为http://192.168.200.130:9000,我们在地址栏输入:http://http://192.168.200.130:9000/ 即可进入登录界面。
Access Key为minio Secret_key为minio123进入系统后可以看到主界面
点击右下角的“+”号 ,点击下面的图标,创建一个桶
3.4 快速入门
3.4.1 创建工程,导入pom依赖
创建minio-demo,对应pom如下
<span style="background-color:#f8f8f8"><span style="color:#333333"><?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></span></span> 
引导类:
<span style="background-color:#f8f8f8"><span style="color:#333333">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);
    }
}</span></span> 
创建测试类,上传html文件
<span style="background-color:#f8f8f8"><span style="color:#333333">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/ak47.jpg");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}</span></span> 
 
3.5 封装MinIO为starter
3.5.1 创建模块heima-file-starter
导入依赖
<span style="background-color:#f8f8f8"><span style="color:#333333"><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></span></span> 
3.5.2 配置类
MinIOConfigProperties
<span style="background-color:#f8f8f8"><span style="color:#333333">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;
}</span></span> 
MinIOConfig
<span style="background-color:#f8f8f8"><span style="color:#333333">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();
    }
}</span></span> 
3.5.3 封装操作minIO类
FileStorageService
<span style="background-color:#f8f8f8"><span style="color:#333333">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);
}</span></span> 
MinIOFileStorageService
<span style="background-color:#f8f8f8"><span style="color:#333333">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();
    }
}</span></span> 
3.5.4 对外加入自动配置
在resources中新建META-INF/spring.factories
<span style="background-color:#f8f8f8"><span style="color:#333333">org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.file.service.impl.MinIOFileStorageService</span></span> 
 
3.5.5 其他微服务使用
第一,导入heima-file-starter的依赖
第二,在微服务中添加minio所需要的配置
<span style="background-color:#f8f8f8"><span style="color:#333333">minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.200.130:9000
  readPath: http://192.168.200.130:9000</span></span> 
第三,在对应使用的业务类中注入FileStorageService,样例如下:
<span style="background-color:#f8f8f8"><span style="color:#333333">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();
        }
    }
}</span></span> 
4)文章详情
4.1)需求分析
4.2)实现方案
方案一
用户某一条文章,根据文章的id去查询文章内容表,返回渲染页面
方案二
4.3)实现步骤
1.在artile微服务中添加MinIO和freemarker的支持,参考测试项目
2.资料中找到模板文件(article.ftl)拷贝到article微服务下
3.资料中找到index.js和index.css两个文件手动上传到MinIO中
4.在文章微服务中导入依赖
<span style="background-color:#f8f8f8"><span style="color:#333333"><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></span></span> 
5.新建ApArticleContentMapper
<span style="background-color:#f8f8f8"><span style="color:#333333">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> {
}</span></span> 
6.在artile微服务中新增测试类(后期新增文章的时候创建详情静态页,目前暂时手动生成)
<span style="background-color:#f8f8f8"><span style="color:#333333">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 {
    @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文件
            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);
        }
    }
}</span></span> 










