黑马头条Day02-app端文章查看,静态化freemarker

news2024/9/24 13:17:06

学习内容:

一、app端文章列表

1. 需求分析

文章的布局展示

2. 导入文章数据库

可以使用IDEA的数据库连接工具执行SQL脚本

3. 表结构分析

ap_article文章基本信息表

ap_article_config文章配置表

ap_article_content文章内容表

把课前资料里提供的实体类复制到model模块下的如下位置

4. 表的拆分-垂直分表

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

优势:

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

拆分规则:

  • 把不常用的字段单独放在一张表
  • 把text、blob等大字段拆分出来单独放在一张表
  • 经常组合查询的字段单独放在一张表中

5. 实现思路

  • 1. 在默认频道展示10条文章信息
  • 2. 可以切换频道查看不同种类文章
  • 3. 当用户下拉可以加载最新的文章(分页)本页文章列表中发布时间为最大的的时间为依据
  • 4. 当用户上拉可以加载更多的文章信息(按照发布时间)本页文章列表中发布时间最小的时间为依据
  • 5. 如果是当前频道的首页,前端传递默认参数:maxBehotTime:0(毫秒)、minBehotTime:20000000000000(毫秒)2063年

6. 接口定义

7. 实现步骤

步骤①:把课前资料里提供的文章微服务复制到service模块下,如图

然后,在pom.xml(heima-leadnews-service)中添加模块声明,刷新maven

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

②在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
    username: root
    password: 123456
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.article.pojos

③在model模块下的article新建ArticleHomeDto

package com.heima.model.article.dtos;

import lombok.Data;

import java.util.Date;

@Data
public class ArticleHomeDto {

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

④在article模块中定义接口

package com.heima.article.controller.v1;

import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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")
@Api("app端文章列表")
public class ArticleHomeController {

    /**
     * 加载首页
     * @param dto
     * @return
     */
    @ApiOperation("加载首页")
    @PostMapping("/load")
    public ResponseResult load(@RequestBody ArticleHomeDto dto) {
        return null;
    }

    /**
     * 加载更多
     * @param dto
     * @return
     */
    @ApiOperation("加载更多")
    @PostMapping("/loadmore")
    public ResponseResult loadmore(@RequestBody ArticleHomeDto dto) {
        return null;
    }

    /**
     * 加载最新
     * @param dto
     * @return
     */
    @ApiOperation("加载最新")
    @PostMapping("/loadnew")
    public ResponseResult loadnew(@RequestBody ArticleHomeDto dto) {
        return null;
    }
}

⑤编写mapper

package com.heima.article.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {

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

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

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

    <resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
        <id column="id" property="id"/>
        <result column="title" property="title"/>
        <result column="author_id" property="authorId"/>
        <result column="author_name" property="authorName"/>
        <result column="channel_id" property="channelId"/>
        <result column="channel_name" property="channelName"/>
        <result column="layout" property="layout"/>
        <result column="flag" property="flag"/>
        <result column="images" property="images"/>
        <result column="labels" property="labels"/>
        <result column="likes" property="likes"/>
        <result column="collection" property="collection"/>
        <result column="comment" property="comment"/>
        <result column="views" property="views"/>
        <result column="province_id" property="provinceId"/>
        <result column="city_id" property="cityId"/>
        <result column="county_id" property="countyId"/>
        <result column="created_time" property="createdTime"/>
        <result column="publish_time" property="publishTime"/>
        <result column="sync_status" property="syncStatus"/>
        <result column="static_url" property="staticUrl"/>
    </resultMap>
    <select id="loadArticleList" resultMap="resultMap">
        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>

<![CDATA[ Your Text Here ]]>,用于定义不需要进行解析的文本数据块。在XML中,当文本内容包含特殊字符,如<,>,&等时,可以使用CDATA部分来告诉解析器不要解析其中的内容。这样做可以避免特殊字符与XML标签混淆,确保文本内容正确显示。

⑦编写业务层代码

在common模块添加常量类ArticleConstants 

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

}

ApArticleService

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;

public interface ApArticleService extends IService<ApArticle> {

    /**
     * 加载文章列表
     * @param dto
     * @param type 1:加载更多,2:加载最新
     * @return
     */
    public ResponseResult load(ArticleHomeDto dto, Short type);
}

ApArticleServiceImpl

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.lang.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 {
    @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);
        dto.setSize(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);
    }
}

⑧编写控制器代码

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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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")
@Api("app端文章列表")
public class ArticleHomeController {

    @Autowired
    private ApArticleService apArticleService;

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

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

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

⑨在nacos的leadnews-app-gateway添加文字微服务的路径

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

⑩swagger测试或前后端联调测试

启动ArticleApplication、AppGatewayApplication和UserApplication

在启动ArticleApplication之前可以先clean一下项目,防止各种报错

图片加载不出来是正常的!!!

二、文章详情

1. 需求分析

2. 实现思路

实现方案一

用户某一条文章,根据文章的id去查询文章内容表,返回渲染页面

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

3. FreeMarker

(1)简介

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

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

(2)技术选型对比

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

(3)环境搭建和快速入门

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

②pom文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>heima-leadnews-test</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>freemarker-demo</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>

③准备配置文件application.yml

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

注意事项:freemarker模板文件通常都是以ftl作为扩展名,也可以为tml、xml、jsp等。

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

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

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

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

⑥创建启动类FreemarkerDemoApplication

package com.heima.freemarker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

⑦创建模型类

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;//钱包
}

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

package com.heima.freemarker.controller;

import com.heima.freemarker.entity.Student;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

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

⑨启动FreemarkerDemoApplication

http://localhost:8881/basic

(4)Freemarker指令语法

基础语法种类

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

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

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

Hello ${name}

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

<#>FTL指令</#>

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

<#--freemarker中的普通文本-->

我是一个普通的文本

集合指令(List和Map)

List指令格式

<#list></#list>

Map

获取map中的值

map['keyname'].property

map.keyname.property

遍历map

<#list userMap?keys as key>

        key:${key}--value:${userMap["$key}"]}

</#list>

①在HelloController中新增如下方法:

package com.heima.freemarker.controller;

import com.heima.freemarker.entity.Student;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

@Controller
public class HelloController {

    // ... ...
    
    @GetMapping( "/list")
    public String list(Model model) {
        Student stu1 = new Student();
        stu1.setName("小强");
        stu1.setAge(18);
        stu1.setMoney(1000.86f);
        stu1.setBirthday(new Date());
        
        Student stu2 = new Student();
        stu2.setName("小红");
        stu2.setAge(19);
        stu2.setMoney(200.1f);
        
        // 将两个对象模型数据存放到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);
        // 向model中存放Map数据
        model.addAttribute("stuMap", stuMap);
        
        return "02-list";
    }
}

②在templates目录下新增02-list.ftl文件

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

<#-- list 数据的展示 -->
<b>展示list中的stu数据:</b>
<br>
<br>
<table>
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#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>
            <#-- 得到循环的下标,使用方法是在stu后边加"_index",它的值是从0开始 -->
            <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>

if指令

<#if expression>

<#else>

</#if>

需求:在list集合中判断学生为小红的数据字体显示为红色。

运算符

数学运算

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

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

比较运算符

逻辑运算符

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

空值处理

1. 判断某变量是否存在使用"??"

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

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

2. 缺失变量默认使用"!"

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

       例:${name!''},表示如果name为空显示空字符串

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

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


内建函数

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

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的作用是定义一个变量。

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

(5)输出静态化文件

使用freemarker原生Api将页面生成html文件

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.setAge(19);
        stu2.setMoney(200.1f);

        // 将两个对象模型数据存放到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.put("today", new Date());
        map.put("point", 1029230122);

        // 返回Map
        return map;
    }
}

4. MinIO

(1)对象存储方式的对比

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

1. 主备服务,高可用

2. 支持主从文件,支持自定义扩展名

3. 支持动态扩容

1. 没有完备官方文档,近几年没有更新

2. 环境搭建较为麻烦

MinIO

1. 性能高,准硬件条件下它能达到55GB/s的读、35GB/s的写速率

2. 部署自带管理界面

3. MinIO.Inc运营的开源项目,社区活跃度高

4. 提供了所有主流开发语言的SDK

1. 不支持动态增加节点

(2)MinIO概述

MinIO基于Apache License v2.0开源协议的对象存储服务,可以作为云存储的解决方案用来保存海量的图片、视频、文档。

  • Golang语言实现,配置简单,单行命令可以运行起来。
  • MinIO兼容亚马逊S3云存储服务接口,适合于存储大容量非结构化的数据,一个对象文件可以是任意大小,从几kb到最大5T不等。
  • 官方文档:MinIO对象存储 Kubernetes — MinIO中文文档 | MinIO Kubernetes中文文档

(3)MinIO特点

特点说明
数据保护MinIO使用MinIO Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的dirver,但是仍然可以从中恢复。
高性能作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率
可扩容不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
SDK支持基于MinIO轻量的特点,它得到类似Java、Python、Go等语言的sdk支持
有操作页面面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源
功能简单这一设计原则让MinIO不容易出错、更快启动
丰富的API支持文件资源的分享连接以及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能
文件变化主动通知存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等

(4)Docker安装MinIO

①拉取镜像

docker pull minio/minio

②创建容器

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

③访问minio系统

http://192.168.200.130:9000/

Access Key:minio、Secret Key:minio23

基本概念:

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

(5)快速入门-上传文件进行静态访问

目标:把list.html文件上传到minio中,并且可以在浏览器中访问

①新建模块minio-demo

②pom.xml内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>heima-leadnews-test</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>minio-demo</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>

③创建引导类MinIOApplication

package com.heima.minio;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

④创建测试类,用于上传html文件

package com.heima.minio.test;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.errors.*;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class MinIOTest {

    /**
     * 把list.html文件上传到minio中,并且可以在浏览器中访问
     * @param args
     */
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;

        try {
            fileInputStream = new FileInputStream("D:\\list.html");
            // 1. 获取minio的链接信息,创建一个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);

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

把bucket的权限修改为Read and Write

http://192.168.200.130:9000/leadnews/list.html

(6)封装MinIO为starter

步骤①:把课前资料里提供的heima-leadnews-basic微服务放到父工程下

在pom.xml(heima-leadnews)添加模块,然后maven刷新

    <modules>
        <!-- -->
        <module>heima-leadnews-test</module>
        <module>heima-leadnews-basic</module>
    </modules>

②在pom.xml(minio-demo)导入heima-file-starter依赖

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

③在微服务minio-demo的application.yml中添加minio所需的配置信息

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

④测试

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;

    // 把list.html文件上传到minio中,并且可以在浏览器中访问
    @Test
    public void test() throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream("D:\\list.html");
        String path = fileStorageService.uploadHtmlFile("", "list.html", fileInputStream);
        System.out.println(path);
    }
}

(7)文章详情实现

实现步骤:

①在article微服务中添加MinIO和freemarker的支持

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

在nacos的leadnews-article文件中添加配置

spring:
  # 。。。 。。。

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

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

③资料中找到index.js、axios.min.js和index.css三个文件手动上传到MinIO中

package com.heima.minio.test;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import java.io.FileInputStream;


public class MinIOTest {


    /**
     * 把list.html文件上传到minio中,并且可以在浏览器中访问
     * @param args
     */
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;

        try {
            fileInputStream = new FileInputStream("D:\\IDEA\\frontProject\\tmp\\js\\index.js");
            // 1. 获取minio的链接信息,创建一个minio的客户端
            MinioClient minioClient = MinioClient.builder()
                    .credentials("minio", "minio123")
                    .endpoint("http://192.168.200.130:9000")
                    .build();

            // 2. 上传
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object("plugins/js/index.js")  // 文件名称
                    .contentType("text/js")  // 文件类型
                    .bucket("leadnews")  // 桶名称,与minio管理界面创建的桶一致即可
                    .stream(fileInputStream, fileInputStream.available(), -1)
                    .build();
            minioClient.putObject(putObjectArgs);

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

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

新建ApArticleContentMapper

package com.heima.article.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.pojos.ApArticleContent;
import org.apache.ibatis.annotations.Mapper;

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

单元测试类ArticleFreemarkerTest

package com.heima.article.test;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.ArticleApplication;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.service.ApArticleService;
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.checkerframework.checker.units.qual.A;
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 ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private Configuration configuration;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private ApArticleService apArticleService;

    @Test
    public void createStaticUrlTest() throws Exception {
        // 已知文章的id
        // 1. 获取文章内容
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers
                .<ApArticleContent>lambdaQuery()
                .eq(ApArticleContent::getArticleId, "1302864436297482242L"));
        if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())) {
            // 2. 文章内容通过freemarker生成html文件
            Template template = configuration.getTemplate("article.ftl");
            // 数据模型
            Map<String, Object> content = new HashMap<>();
            content.put("content", JSONArray.parseArray(apArticleContent.getContent()));
            // 输出流
            StringWriter out = new StringWriter();
            template.process(content, out);

            // 3. 把html文件上传到minio中
            InputStream in = new ByteArrayInputStream(out.toString().getBytes());
            String path = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", in);

            // 4. 修改ap_article表,保存static_url字段
            apArticleService.update(Wrappers
                    .<ApArticle>lambdaUpdate()
                    .eq(ApArticle::getId, apArticleContent.getArticleId())
                    .set(ApArticle::getStaticUrl, path));
        }
    }
}

访问测试:http://192.168.200.130:9000/leadnews/2024/07/17/1302864436297482242.html

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

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

相关文章

转移C盘中的conda环境(包括.condarc文件修改,environment.txt文件修改,conda报错)

conda环境一般是默认安装到C盘的&#xff0c;若建立多个虚拟环境&#xff0c;时间长了&#xff0c;容易让本不富裕的C盘更加雪上加霜&#xff0c;下面给出将conda环境从C盘转移到D盘的方法。 目录 电脑软硬件转移方法查看当前conda目录转移操作第一步&#xff1a;.condarc文件修…

vmware虚拟机中,Centos安装Docker,解决国内无法访问

背景 本文主要解决了centos 的yum源 无法更新 和 docker的 源 国内无法访问的问题。 本文环境&#xff1a;windows宿主机装了vmware虚拟机&#xff0c;虚拟机中安装了Centos&#xff0c;centos内装docker。其实可以直接在window装docker desktop for windows&#xff0c;但…

ns3-gym入门(三):在opengym基础上实现一个小小的demo

因为官方给的"opengym""opengym-2"这两个例子都很简单&#xff0c;所以自己改了一个demo&#xff0c;把reward-action-state相互影响的关系表现出来 一、准备工作 在ns3.35/scratch目录下创建一个文件夹&#xff1a; &#xff08;后续的运行指令后面都需要…

JVM---对象是否存活及被引用的状态

1.如何判断对象是否存活 1.1 引用计数算法 概念&#xff1a;在对象头部增加一个引用计数器,每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1b;任何时刻计数器为零的对象就是不可能再被使用的。 优点&#xff1…

喰星云·数字化餐饮服务系统 多处 SQL注入漏洞复现

0x01 产品简介 喰星云数字化餐饮服务系统是一款专为餐饮企业设计的综合性管理软件,旨在通过信息化手段提升餐饮企业的运营效率、降低运营成本,并实现数据驱动的决策管理。该系统包括供应链管理、财务管理、巡店管理、人力资源管理等多个模块,可全面覆盖餐饮企业的日常运营需…

【多线程综合】java何时考虑线程安全问题、怎么考虑、又怎么解决?

前言&#xff1a;在编程中&#xff0c;线程安全是一个非常重要的概念。它涉及到多个线程并发访问共享资源时的正确性和一致性。在Java中&#xff0c;为了确保线程安全&#xff0c;我们需要考虑一些关键因素。 1、什么是线程安全 线程安全是指当多个线程同时访问一个对象时&am…

十大排序 之 快速排序

&#xff01;&#xff01;&#xff01;排序仅针对于数组哦本次排序是按照升序来的哦代码后边有图解哦 介绍 快速排序英文名为Quick Sort 基本思路 快速排序采用的是分治思想&#xff0c;即在一个无序的序列中选取一个任意的基准元素base&#xff0c;利用base将待排序的序列分…

基于RK3588的8K视频解码显示案例分享!引领超高清工业视频时代

8K、4K、2K显示对比 2K分辨率&#xff1a;也称为全高清&#xff08;FULL HD&#xff09;&#xff0c;它具有1920 x 1080像素的分辨率。这是目前大多数消费者电视和电脑显示器的标准分辨率&#xff0c;可以提供良好的图像质量。 4K分辨率&#xff1a;也称为4K超高清&#xff0…

我无法给博客园出钱,那我就出点建议吧

相信这张图大家都已经看见过了&#xff0c;从去年就传出博客园经营困难的情况&#xff0c;其实很多平台&#xff0c;不止是博客园&#xff0c;包括现在国内的很多公司都一样&#xff0c;经营是一件大难题&#xff0c;但很多公司我们不知道&#xff0c;悄无声息的倒下了。而博客…

【雅思备考IELTS】写作第一部分Writing Part One

Tips for IELTS Writing (Part 1) By James Lee 2024/7/15 Part 1: 图表数据分析 Analysis of a Graph / Chart / Curve, etc. 这部分一般是让分析一张图表&#xff08;Graph或Chart&#xff09;&#xff0c;用时约20分钟&#xff0c;字数不用太多&#xff0c;150词以上即可。…

推荐一个可以体验正版ChatGPT的平台

在鱼龙混杂的API市场&#xff0c;智创聚合API以其卓越的性能和创新的服务理念&#xff0c;为用户带来了前所未有的体验。我们自豪地宣布&#xff0c;现在加入我们的限时官方API渠道&#xff0c;您将享受到更快速率提升&#xff0c;以及更高质量的回复服务&#xff0c;而这些仅需…

Python酷库之旅-第三方库Pandas(028)

目录 一、用法精讲 71、pandas.tseries.api.guess_datetime_format函数 71-1、语法 71-2、参数 71-3、功能 71-4、返回值 71-5、说明 71-6、用法 71-6-1、数据准备 71-6-2、代码示例 71-6-3、结果输出 72、pandas.util.hash_array函数 72-1、语法 72-2、参数 72…

【PostgreSQL】PostgreSQL简史

博主介绍&#xff1a;✌全网粉丝20W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…

Java SHA-256哈希算法

一、SHA-256算法简介 SHA-2&#xff08;Secure Hash Algorithm 2&#xff09;&#xff0c;一种散列函数算法标准&#xff0c;由美国国家安全局研发&#xff0c;由美国国家标准与技术研究院&#xff08;NIST&#xff09;在2001年发布&#xff0c;属于SHA算法之一&#xff0c;是…

C++ std::lock_guard和 std::unique_lock

二者都是 C 标准库中用于管理互斥锁&#xff08;mutex&#xff09;的 RAII&#xff08;Resource Acquisition Is Initialization&#xff09;机制的类。这些类可以确保互斥锁在构造时被获取&#xff0c;在析构时被释放&#xff0c;从而避免死锁和资源泄漏问题。不过&#xff0c…

使用windows批量解压和布局ImageNet ISLVRC2012数据集

使用的系统是windows&#xff0c;找到的解压命令很多都linux系统中的&#xff0c;为了能在windows系统下使用&#xff0c;因此下载Git这个软件&#xff0c;在其中的Git Bash中使用以下命令&#xff0c;因为Git Bash集成了很多linux的命令&#xff0c;方便我们的使用。 ImageNe…

【博士每天一篇文献-算法】 PNN网络启发的神经网络结构搜索算法Progressive neural architecture search

阅读时间&#xff1a;2023-12-23 1 介绍 年份&#xff1a;2018 &#xff1a;Chenxi Liu,Google DeepMind研究科学家;Barret Zoph,OpenAI;Maxim Neumann,Goolge 会议&#xff1a;B区会议&#xff0c; Proceedings of the European conference on computer vision (ECCV). 引用…

【Android14 ShellTransitions】(七)Transition就绪

Transition.onTransactionReady的内容比较长&#xff0c;我们挑重点的部分逐段分析&#xff08;跳过的地方并非不重要&#xff0c;而是我柿子挑软的捏&#xff09;。 1 窗口绘制状态的流转以及显示SurfaceControl 注意我们这里的SurfaceControl特指的是WindowSurfaceControll…

Excel办公技巧:制作二级联动下拉菜单

分享制作二级联动下拉菜单的方法&#xff0c;即使数据有增删&#xff0c;菜单也能自动更新&#xff01; 可以通过先定义名称&#xff0c;再结合数据验证&#xff0c;来做二级联动下拉菜单。 1. 准备数据 首先&#xff0c;我们需要准备好要进行二级联动下拉菜单的数据&#xff…

K8S 上部署 Emqx

文章目录 安装方式一&#xff1a;快速部署安装方式二&#xff1a;定制化部署1. 使用 Pod 直接部署 EMQX Broker2. 使用 Deoloyment 部署 Pod3. 使用 Services 公开 EMQX Broker Pod 服务4. 通过 kubernetes 自动集群 EMQX MQTT 服务器5. 修改 EMQX Broker 的配置6. 赋予 Pod 访…