【黑马头条】-day02文章列表查看-freemarker-MinIO


文章目录


1 app端文章列表分析

1.1 需求分析

在这里插入图片描述

1.2 导入文章数据库

在这里插入图片描述

1.3 表结构分析

在这里插入图片描述

在这里插入图片描述

将文章实体类放入heima-leadnews-model模块下的com.heima.model.article.pojos包下

在这里插入图片描述

1.3.1 表的拆分

在这里插入图片描述

1.4 SQL实现思路

在这里插入图片描述

先写sql

# 按照发布时间倒叙查询十条文章
select * from ap_article aa order by aa.publish_time desc limit 10 
# 频道筛选
select * from ap_article aa where aa.channel_id=1 order by aa.publish_time desc limit 10 
# 加载首页
select * from ap_article aa 
where aa.channel_id=1 and aa.publish_time<'2063-04-19 00:20:17'
order by aa.publish_time desc limit 10 
# 加载更多-2020-09-07 22:30:09
select * from ap_article aa 
where aa.channel_id=1 and aa.publish_time<'2020-09-07 22:30:09'
order by aa.publish_time desc limit 10 
# 加载最新数据
select * from ap_article aa 
where aa.channel_id=1 and aa.publish_time>'2020-09-07 22:30:09'
order by aa.publish_time desc limit 10 
# 最终--------------------------------------------------------------------------------------------
# 结合权限控制
select * from ap_article aa left join ap_article_config aac on aa.id=aac.article_id
where aac.is_down != 1 and aac.is_delete !=1 
and aa.channel_id=1 and aa.publish_time>'2020-09-07 22:30:09'
order by aa.publish_time desc limit 10 

1.5 接口定义

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2 实现app端文章列表

2.1 导入微服务并添加配置

现在heima-leadnews-service中只有heima-leadnews-user这一个微服务,还要导入article这个微服务

将heima-leadnews-article放入heima-leadnews-service文件夹中,再修改heima-leadnews-service的pom文件

<parent>
    <artifactId>heima-leadnews</artifactId>
    <groupId>com.heima</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<packaging>pom</packaging>
<modules>
    <module>heima-leadnews-user</module>
</modules>
<modelVersion>4.0.0</modelVersion>

在modules标签中导入heima-leadnews-article

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

在meavn中刷新

在这里插入图片描述

已经配置正常

剩下mysql等配置在Nacos中进行配置

在这里插入图片描述

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

2.2 定义Controller

在heima-leadnews-article模块下的com.heima.article.controller.v1包下创建ArticleHomeController

@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
    /**
     * 加载首页
     * @param dto
     * @return
     */
    @PostMapping("/load")
    public String load(@RequestBody(required = false) ArticleHomeDto dto) {
        return "success";
    }

    /**
     * 加载更多
     * @param dto
     * @return
     */
    @PostMapping("/loadmore")
    public String loadmore(@RequestBody(required = false)ArticleHomeDto dto) {
        return "success";
    }

    /**
     * 加载最新
     * @param dto
     * @return
     */
    @PostMapping("/loadnew")
    public String loadnew(@RequestBody(required = false)ArticleHomeDto dto) {
        return "success";
    }
}

在heima-leadnews-model模块下com.heima.model.article.dtos包下创建ArticleHomeDto dto类

@Data
public class ArticleHomeDto {
    private Date maxBehotTime;
    private Date minBehotTime;
    private Integer size;
    private String tag;
}

2.3 编写mapper

在com.heima.article.mapper创建ApArticleMapper接口

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

编写mybatisplus的mapper,在resource中的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">
        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>

2.4 编写业务层Service

在com.heima.article.service创建ApArticleService接口

public interface ApArticleService extends IService<ApArticle>{
    /**
     * 加载文章列表
     * @param dto
     * @param type 1 加载更多 2 加载最新
     * @return
     */
    public ResponseResult load(ArticleHomeDto dto, Short type);
}

在impl包下创建其实现类ApArticleServiceImpl

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

    private final static Short MAX_PAGE_SIZE = 50;

    /**
     * 加载文章列表
     * @param dto
     * @param type 1 加载更多 2 加载最新
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, Short type) {
        //1.参数检查
        //1.1 分页条数校验
        Integer size = dto.getSize();
        if(size == null || size <= 0){
            size = 10;
        }
        //分页不超过50
        size=Math.min(size,MAX_PAGE_SIZE);
        //1.2 校验参数type
        if(!type.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && !type.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){
            type = ArticleConstants.LOADTYPE_LOAD_MORE;
        }
        //1.3 频道参数校验
        if(StringUtils.isBlank(dto.getTag())){
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }
        //1.4 时间参数校验
        if(dto.getMaxBehotTime() == null){
            dto.setMaxBehotTime(new Date());
        }
        if(dto.getMinBehotTime() == null){
            dto.setMinBehotTime(new Date());
        }
        //2.查询数据
        List<ApArticle> articleList = apArticleMapper.loadArticleList(dto, type);
        //3.结果返回
        return ResponseResult.okResult(articleList);
    }
}

为了校验参数type,定义常量类

在heima-leadnews-common模块下的com.heima.common.constants包下创建常量类ArticleConstants

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__";

}

2.5 编写控制层

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

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

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

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

2.6 前后端联调

可以直接发起POST请求http://localhost:51802/api/v1/article/loadmore

{
    "maxBehotTime": "",
    "minBehotTime": "",
    "size": 37,
    "tag": ""
}

返回

{
    "host": null,
    "code": 200,
    "errorMessage": "操作成功",
    "data": [
        {
            "id": 1383827787629252610,
            "title": "Kafka文件的存储机制",
            "authorId": 4,
            "authorName": "admin",
            "channelId": 1,
            "channelName": "java",
            "layout": 1,
            "flag": null,
            "images": "http://192.168.200.130:9000/leadnews/2021/4/20210418/4a498d9cf3614570ac0cb2da3e51c164.jpg",
            "labels": null,
            "likes": null,
            "collection": null,
            "comment": null,
            "views": null,
            "provinceId": null,
            "cityId": null,
            "countyId": null,
            "createdTime": "2021-04-18T17:00:29.000+00:00",
            "publishTime": "2021-04-18T16:20:17.000+00:00",
            "syncStatus": false,
            "origin": false,
            "staticUrl": null
        },

说明功能测试没问题,但没有加网关

2.7 添加网关

在Nacos中修改leadnews-app-gatesway的配置

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      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

访问黑马头条

正常访问,图片是因为没有OSS服务显示不出来

2 文章详情分析

2.1 需求分析

2.1.1 思路一

在这里插入图片描述

2.1.2 思路二-推荐

在这里插入图片描述

2.2 Freemarker

2.2.1 概述

在这里插入图片描述

在这里插入图片描述

2.2.2 环境搭建

2.2.2.1 创建模块导入依赖

在heima-leadnews-test模块下创建模块freemarker-demo模块,然后导入依赖

<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>
2.2.2.2 编写配置文件

在resource中创建application.yaml

在这里插入图片描述

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

2.2.3 快速入门

在这里插入图片描述

2.2.3.1 创建01-basic.ftl

在templates目录下创建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.3.2 创建引导类

创建com.heima.freemarker.FreemarkerDemoApplication引导类

@SpringBootApplication
public class FreemarkerDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(FreemarkerDemoApplication.class, args);
    }
}
2.2.3.3 创建学生类

创建com.heima.freemarker.entity.Student实体类

@Data
public class Student {
    private String name;//姓名
    private int age;//年龄
    private Date birthday;//生日
    private Float money;//钱包
}
2.2.3.4 创建Controller

创建com.heima.freemarker.controller.HelloController实体类

@Controller
public class HelloController {
    @GetMapping("/basic")
    public String hello(Model model){
        //name
        model.addAttribute("name","黑马程序员");
        //stu
        Student stu = new Student();
        stu.setName("小明");
        stu.setAge(18);
        model.addAttribute("stu",stu);
        return "01-basic";
    }
}

@RestController是返回json字符串,这个并不是返回字符串,所以用@Controller就可以。

2.2.3.5启动测试

访问页面 /basic

2.2.3.6 原理解释

freemarker的自动配置类里有当前模板的默认位置,会找到结尾以.ftlh的模板文件,一般为ftl

后缀不唯一,所以要在application.yaml的suffix: .ftl要重新文件类型

2.2.3.7 总结

在这里插入图片描述

2.2.4 基础语法

在这里插入图片描述

2.2.4.1 集合指令-list

在这里插入图片描述

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

编写controller

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

使用集合指令访问List stus

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

访问/list

在这里插入图片描述

2.2.4.2 集合指令-map

修改02-list.ftl

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

重新访问

在这里插入图片描述

刚刚只是拿出map中的单个key,现在需要遍历Map

<#list stuMap?keys as key></#list>

<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+1}</td>
            <td>${stuMap[key].name}</td>
            <td>${stuMap[key].age}</td>
            <td>${stuMap[key].money}</td>
        </tr>
    </#list>
</table>

在这里插入图片描述

2.2.4.3 if指令

在freemarker中,=和==是一样的

在这里插入图片描述

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

在这里插入图片描述

2.2.4.4 运算符-数学运算

在这里插入图片描述

2.2.4.5 比较运算符

在这里插入图片描述

2.2.4.6 逻辑运算符

在这里插入图片描述

2.2.4.7 空值处理

在这里插入图片描述

<#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.2.4.8 内建函数
1)集合大小?size

在这里插入图片描述

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

在这里插入图片描述

2)日期格式化?date ?time ?datetime ?string

在这里插入图片描述

修改controller传入today

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

修改ftl

当前date日期为:${today?date}<br/>
当前time日期为:${today?time}<br/>
当前datetime日期为:${today?datetime}<br/>
当前自定义日期为:${today?string("yyyy-MM-dd HH:mm:ss")}<br/>

在这里插入图片描述

3)长数字去逗展示?c

在这里插入图片描述

controller中添加

//长数值
model.addAttribute("money",12345678910111213L);

修改ftl,如果没有?c,则是三个一逗号,三个一逗号

展示长数值:${longnumber?c}<br/>

在这里插入图片描述

4)json转对象?eval

在这里插入图片描述

修改controller

//json
Student stu3 = new Student();
stu3.setName("黑红");
stu3.setMoney(999.1f);
stu3.setAge(21);
//对象转为JSON
String jsonStu = JSON.toJSONString(stu3);
model.addAttribute("jsonStu",jsonStu);

修改ftl

<#assign stuData=jsonStu?eval>
名字:${stuData.name}<br/>
年龄:${stuData.age}<br/>
钱包:${stuData.money}<br/>

在这里插入图片描述

2.2.5 输出静态化文化

在这里插入图片描述

2.2.5.1 生成静态文件

在heima-leadnews-test模块下的freemarker-demo模块的test文件夹下的com.heima.freemarker.test生成FreemarkerTest类

@SpringBootTest(classes= FreemarkerDemoApplication.class)
@RunWith(SpringRunner.class)
public class FreemarkerTest {
    @Autowired
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
        Template template = configuration.getTemplate("02-list.ftl");
        /**
         * 合成方法
         * 参数一:模型数据
         * 参数二:输出流
         */
        template.process(getData(),new FileWriter("D:\\Code\\JavaCode\\HeimaToutiao\\static-html"));
    }


    private Map getData(){
        Map<String, Object> map = new HashMap<>();

        //------------------------------------
        Student stu1 = new Student();
        stu1.setName("小强");
        stu1.setAge(18);
        stu1.setMoney(1000.86f);
        stu1.setBirthday(new Date());

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

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

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

        //------------------------------------

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

        //日期
        map.put("today",new Date());

        //长数值
        map.put("longnumber",12345678910111213L);

        return map;
    }
}
2.2.5.2 为configuration定位模板地址

因为configuration.getTemplate并不知道模板文件的地址,因此要在appication.yaml中添加属性

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 #指定模板文件的位置
2.2.5.3 访问静态资源

最后在D:\Code\JavaCode\HeimaToutiao\static-html成功生成静态文件

访问

在这里插入图片描述

2.3 MinIO

2.3.1 概述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.3.2 安装MinIO

从tar包加载MinIO

docker load -i minio.tar

创建容器

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

访问9000端口 192.168.204.129:9000

在这里插入图片描述

进入minio

在这里插入图片描述

2.3.3 基本概念

在这里插入图片描述

在这里插入图片描述

2.3.4 快速入门

在这里插入图片描述

2.3.4.1 创建minio-demo模块并导入依赖

在heima-leadnews-test模块下创建minio-demo模块并导入依赖

<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>
2.3.4.2 创建启动类MinIOApplication

创建其启动类com.heima.minio.MinIOApplication

@SpringBootApplication
public class MinIOApplication {
    public static void main(String[] args) {
        SpringApplication.run(MinIOApplication.class, args);
    }
}
2.3.4.3创建测试类MinIOTest

在test目录下创建com.heima.minio.test.MinIOTest

@SpringBootTest(classes= MinIOApplication.class)
@RunWith(SpringRunner.class)
public class MinIOTest {

    /**
     * 把list.html文件上传到minio中,并且可以在浏览器中访问
     */
    @Test
    public void test() {
        //0.读文件获取文件流
        String file_path="d:/Code/JavaCode/HeimaToutiao/static-html/list.html";
        try {
            FileInputStream fileInputStream = new FileInputStream(file_path);
            //1. 获取minio的链接信息,创建一个minio的客户端
            MinioClient minioClient = MinioClient.builder()
                    .credentials("minio", "minio123")
                    .endpoint("http://192.168.204.129:9000")
                    .build();

            //2. 使用putObject上传一个文件
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object("list.html")//指定上传的文件名
                    .contentType("text/html")//指定文件的类型
                    .bucket("leadnews")
                    .stream(fileInputStream, fileInputStream.available(), -1).build();
            minioClient.putObject(putObjectArgs);

            //3.访问路径
            System.out.println("http://192.168.204.129:9000/leadnews/list.html");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

运行测试方法,已经上传成功

在这里插入图片描述

但是无法访问

在这里插入图片描述

修改桶的权限,重新上传

在这里插入图片描述

访问成功

2.3.5 将minio封装为starter

在这里插入图片描述

在这里插入图片描述

2.3.5.1 引入heima-leadnews-basic

把heima-leadnews-basic模块放入heima-leadnews模块,并且修改heima-leadnews的pom依赖

<modules>
    <module>heima-leadnews-common</module>
    <module>heima-leadnews-utils</module>
    <module>heima-leadnews-model</module>
    <module>heima-leadnews-feign-api</module>
    <module>heima-leadnews-service</module>
    <module>heima-leadnews-gateway</module>
    <module>heima-leadnews-test</module>
    <module>heima-leadnews-basic</module>
</modules>
2.3.5.2 为minio-demo引入basic依赖
<dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-file-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
2.3.5.3 创建配置文件application.yaml
minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.204.129:9000
  readPath: http://192.168.204.129:9000

在这里插入图片描述

因为已经引入heima-file-starter,其中依赖一一对应,@ConfigurationProperties(prefix = "minio") minio开头

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

MinIOConfigProperties负责匹配applicatio.yaml中的属性

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

MinIOConfig负责用MinIOConfigProperties读到的属性完成对MinioClient的初始化,并且通过@Configuration覆盖spring中原始的MinioClient,完成自动装配,然后heima-file-starter的service层的FileStorageService接口的实现类MinIOFileStorageService注入两个自动装配好的容器

@Autowired
private MinioClient minioClient;

@Autowired
private MinIOConfigProperties minIOConfigProperties;

实现业务,外界可以通过注入FileStorageService接口来完成对操作的访问。

2.3.5.4 minio-demo使用FileStorageService
@Autowired
private FileStorageService fileStorageService;

/**
 * 使用FileStorageService上传文件
 */
@Test
public void useFileStorageService() throws FileNotFoundException {
    String file_path="d:/Code/JavaCode/HeimaToutiao/static-html/list.html";
    FileInputStream fileInputStream = new FileInputStream(file_path);
    String path = fileStorageService.uploadHtmlFile("", "list.html", fileInputStream);
    System.out.println(path);
}

uploadHtmlFile三个参数,第一个参数前缀,不需要,第二个文件名,第三个输入流

在这里插入图片描述

查看minio

在这里插入图片描述

3 文章详情实现

3.1 总体步骤

在这里插入图片描述

3.2 实现步骤

在这里插入图片描述

在这里插入图片描述

3.2.1 为article微服务添加minio和freemarker支持

为article微服务引入minio和freemarker支持,修改pom文件

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

并且添加配置文件application.yaml

但是文章微服务已经交给nacos管理了,所以可以直接在nacos的文章的配置中心进行配置热更新,修改Nacos中leadnews-article,然后进行发布

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

3.2.2 拷贝模板文件到article服务下

在heima-leadnews-service模块下的heima-leadnews-article模块下的resource文件夹创建templates文件夹,放入模板文件article.ftl

在这里插入图片描述

3.2.3 手动上传js和css文件到minio中

上传index.css,要修改file_path、object、contentType

@Test
public void testNormal() {
    //0.读文件获取文件流
    String file_path="D:\\Code\\JavaCode\\HeimaToutiao\\static-html\\plugins\\css\\index.css";
    try {
        FileInputStream fileInputStream = new FileInputStream(file_path);
        //1. 获取minio的链接信息,创建一个minio的客户端
        MinioClient minioClient = MinioClient.builder()
                .credentials("minio", "minio123")
                .endpoint("http://192.168.204.129:9000")
                .build();

        //2. 使用putObject上传一个文件
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .object("plugins/css/index.css")//指定上传的文件名
                .contentType("text/css")//指定文件的类型
                .bucket("leadnews")
                .stream(fileInputStream, fileInputStream.available(), -1).build();
        minioClient.putObject(putObjectArgs);

        //3.访问路径
        System.out.println("http://192.168.204.129:9000/leadnews/list.html");
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

上传成功

index.js同理

@Test
public void testNormal() {
    //0.读文件获取文件流
    String file_path="D:\\Code\\JavaCode\\HeimaToutiao\\static-html\\plugins\\js\\index.js";
    try {
        FileInputStream fileInputStream = new FileInputStream(file_path);
        //1. 获取minio的链接信息,创建一个minio的客户端
        MinioClient minioClient = MinioClient.builder()
                .credentials("minio", "minio123")
                .endpoint("http://192.168.204.129:9000")
                .build();

        //2. 使用putObject上传一个文件
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .object("plugins/js/index.js")//指定上传的文件名
                .contentType("text/js")//指定文件的类型
                .bucket("leadnews")
                .stream(fileInputStream, fileInputStream.available(), -1).build();
        minioClient.putObject(putObjectArgs);

        //3.访问路径
        System.out.println("http://192.168.204.129:9000/leadnews/list.html");
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

3.2.4 新增测试类ArticleFreemarkerTest

heima-leadnews-article模块下的test下创建测试类com.heima.article.test.ArticleFreemarkerTest

@SpringBootTest(classes= ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {

    @Test
    public void testFreemarker() {
        //1.获取文章内容(已知文章id)

        //2.文章内容通过freemarker生成静态html页面

        //3.把静态页面上传到minio

        //4.把静态页面的路径保存到数据库
    }

}

但是现在还没有获取文章内容的mapper,因此需要先创建mapper

3.2.5 编写获取文章内容的mapper

@Mapper
public interface ApArticleContentMapper extends BaseMapper<ApArticleContent> {
}

3.2.6 完善测试类ArticleFreemarkerTest

@SpringBootTest(classes= ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private Configuration configuration;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private ApArticleService apArticleService;

    @Test
    public void testFreemarker() throws Exception {
        //1.获取文章内容(已知文章id:1303156149041758210)
        ApArticleContent apArticleContent = apArticleContentMapper
                .selectOne(Wrappers
                        .<ApArticleContent>lambdaQuery()
                        .eq(ApArticleContent::getArticleId, "1303156149041758210L"));
        if(apArticleContent!=null&& StringUtils.isNotBlank(apArticleContent.getContent())){
            //2.文章内容通过freemarker生成静态html页面
            Template template = configuration.getTemplate("article.ftl");
            //2.1 创建模型
            Map<String,Object> content=new HashMap();
            //content是固定的,因为article.ftl中有<#if content??>${content}</#if>
            //因为apArticleContent.getContent()获取的是字符串,所以需要转换成对象
            content.put("content", JSONArray.parseArray(apArticleContent.getContent()));
            //2.2 输出流
            StringWriter writer = new StringWriter();
            //2.3 合成方法
            template.process(content,writer);
            //3.把静态页面上传到minio
            //3.1 文件流
            InputStream inputStream = new ByteArrayInputStream(writer.toString().getBytes());
            String path = fileStorageService.uploadHtmlFile("",apArticleContent.getArticleId()+".html",inputStream);
            //4.把静态页面的路径保存到数据库
            apArticleService.update(Wrappers
                    .<ApArticle>lambdaUpdate()
                    .eq(ApArticle::getId,apArticleContent.getArticleId())
                    .set(ApArticle::getStaticUrl,path));
        }
    }
}

成功在数据库更新

在这里插入图片描述

访问http://192.168.204.129:9000/leadnews/2024/03/21/1303156149041758210.html

在这里插入图片描述

3.2.7 联调测试

启动网关和User

在这里插入图片描述

再启动nginx

nginx

打开浏览器localhost:8801

在这里插入图片描述

访问成功

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bblb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值