app端文章列表
学习内容
需求分析
上方分类频道切换
布局,无图,单图,三张图
文章数据库表
导入文章数据库
结构分析
配置-文章 一对一,拆表,冷热数据分离满足范式
表的拆分-垂直分表
优势
查文章信息不会连带查询文章内容
将longtext字段拆除去,高频查询提高性能
拆分规则
- 不常用的放一起
- text,blob放一起
- 组合查询字段放一起
拷贝实体类
dtos包下
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;
}
pojos包下
package com.heima.model.article.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 文章信息表,存储已发布的文章
* </p>
*
* @author itheima
*/
@Data
@TableName("ap_article")
public class ApArticle implements Serializable {
@TableId(value = "id",type = IdType.ID_WORKER)
private Long id;
/**
* 标题
*/
private String title;
/**
* 作者id
*/
@TableField("author_id")
private Long authorId;
/**
* 作者名称
*/
@TableField("author_name")
private String authorName;
/**
* 频道id
*/
@TableField("channel_id")
private Integer channelId;
/**
* 频道名称
*/
@TableField("channel_name")
private String channelName;
/**
* 文章布局 0 无图文章 1 单图文章 2 多图文章
*/
private Short layout;
/**
* 文章标记 0 普通文章 1 热点文章 2 置顶文章 3 精品文章 4 大V 文章
*/
private Byte flag;
/**
* 文章封面图片 多张逗号分隔
*/
private String images;
/**
* 标签
*/
private String labels;
/**
* 点赞数量
*/
private Integer likes;
/**
* 收藏数量
*/
private Integer collection;
/**
* 评论数量
*/
private Integer comment;
/**
* 阅读数量
*/
private Integer views;
/**
* 省市
*/
@TableField("province_id")
private Integer provinceId;
/**
* 市区
*/
@TableField("city_id")
private Integer cityId;
/**
* 区县
*/
@TableField("county_id")
private Integer countyId;
/**
* 创建时间
*/
@TableField("created_time")
private Date createdTime;
/**
* 发布时间
*/
@TableField("publish_time")
private Date publishTime;
/**
* 同步状态
*/
@TableField("sync_status")
private Boolean syncStatus;
/**
* 来源
*/
private Boolean origin;
/**
* 静态页面地址
*/
@TableField("static_url")
private String staticUrl;
}
package com.heima.model.article.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* APP已发布文章配置表
* </p>
*
* @author itheima
*/
@Data
@TableName("ap_article_config")
public class ApArticleConfig implements Serializable {
@TableId(value = "id",type = IdType.ID_WORKER)
private Long id;
/**
* 文章id
*/
@TableField("article_id")
private Long articleId;
/**
* 是否可评论
* true: 可以评论 1
* false: 不可评论 0
*/
@TableField("is_comment")
private Boolean isComment;
/**
* 是否转发
* true: 可以转发 1
* false: 不可转发 0
*/
@TableField("is_forward")
private Boolean isForward;
/**
* 是否下架
* true: 下架 1
* false: 没有下架 0
*/
@TableField("is_down")
private Boolean isDown;
/**
* 是否已删除
* true: 删除 1
* false: 没有删除 0
*/
@TableField("is_delete")
private Boolean isDelete;
}
package com.heima.model.article.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("ap_article_content")
public class ApArticleContent implements Serializable {
@TableId(value = "id",type = IdType.ID_WORKER)
private Long id;
/**
* 文章id
*/
@TableField("article_id")
private Long articleId;
/**
* 文章内容
*/
private String content;
}
sql实现思路
默认展示10条文章
切换频道
下拉刷新,新数据必须大于当前10条数据最新的那条,由于是降序排列,时间大于第一条,《什么是java语言》
上拉加载,新数据必须小于当前10条数据最晚那条,也就是最下面的那条信息,因为要加载早期发布的,所以其时间戳要小一点
如果为首页,就展示十条最新数据即可,时间戳小于2063年
查询语句
SELECT * from ap_article aa
LEFT JOIN ap_article_config aac ON aa.id=aac.article_id
WHERE publish_time >'2020-09-08'
AND channel_id=1
AND aac.is_down!=1
AND aac.is_delete!=1
ORDER BY created_time DESC
LIMIT 10
最新10,加载老一点的10,左连接条件约束不能下架且不能删除,降序查
接口实现
最大时间,最小时间,分页大小,频道id
步骤
注意,解压缩后的目录不能直接复制粘贴,外面多了一层同名的目录
导入微服务
导入成功后的文件前面有个小蓝点,否则在里面创建不了class
nacos添加配置
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
路由服务增加新的服务
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
定义接口
package com.heima.article.controller.v1;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
@PostMapping("/load")
public ResponseResult load(@RequestBody ArticleHomeDto dto) {
return null;
}
@PostMapping("/loadmore")
public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
return null;
}
@PostMapping("/loadnew")
public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
return null;
}
}
编写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> {
/**
* 加载文章列表 1为加载更多,2为加载最新
* @param dto
* @param type
* @return
*/
public List<ApArticle> loadArticleList(@Param("dto") ArticleHomeDto dto, @Param("type") Short type);
}
编写xml
(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>
<if test="dto.tag != '__all__'">
and aa.channel_id = #{dto.tag}
</if>
意思为不为推荐页,则按频道分类
常量
由于1,2难分辨代表啥意思,我们抽成常量类
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
@Service
package com.heima.article.service.impl;
import com.alibaba.cloud.commons.lang.StringUtils;
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 org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
import java.util.List;
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
@Autowired
ApArticleMapper apArticleMapper;
/**
* 加载文章列表
*
* @param dto
* @param type 1为加载更多,2为加载最新
* @return
*/
@Override
public ResponseResult load(ArticleHomeDto dto, Short loadtype) {
//参数校验
//判断大小是否正确
Integer size = dto.getSize();
if(size==null||size==0){
size=Math.min(size,50);
}
//类型参数检验,既不为1,加载更多也不为2加载最新,那么就默认1加载更多
if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){
loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
}
//文章频道校验,如果不指定频道,那就是首页,直接加载最新10条
if(StringUtils.isEmpty(dto.getTag())){
dto.setTag(ArticleConstants.DEFAULT_TAG);
}
//时间校验。如果没有最大和最小时间,那么说明时间范围为无限,此时降序展示10条最新数据,与前面的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);
return ResponseResult.okResult(apArticles);
}
}
接口完善
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(dto, ArticleConstants.LOADTYPE_LOAD_MORE);
}
@PostMapping("/loadmore")
public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_MORE);
}
@PostMapping("/loadnew")
public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_NEW);
}
}
路由服务增加转发列表
在nacos里
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 用户微服务
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
测试加载更多和最新
app文章详情
实现方案一
根据id查
实现方案2
内容通过模板技术生成html文件
存入分布式系统minIO,同时有一个url存入数据库
用户直接访问url,不需要查库
大公司做longtext和blob时使用
freemarker
介绍
模板引擎
模板+数据生成HTML
模板编写全拼为 freemarker template language FTL
常见java模板引擎
jsp(javaWeb里的servlet,前后端不分离),
freemarker 好,快
thymeleaf 好,但慢
velocity 不好
作为springmvc视图格式,且支持渲染视图
环境搭建
在测试模块下创建子工程,并添加依赖
依赖
<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>
application.yml配置文件
server:
port: 8881 #服务端口
spring:
application:
name: freemarker-demo #指定服务名
freemarker:
cache: false #关闭模板缓存,方便测试
settings:
template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
suffix: .ftl #指定Freemarker模板文件的后缀名
关缓存,修改数据实时更新
启动类
package com.heima.freemarker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.swing.*;
@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;//钱包
}
在resources下创建templates文件夹,存入模板文件
对象啥数据,模板就啥数据
<!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>
接口
package com.heima.freemarker.controller;
import com.heima.freemarker.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.GetMapping;
// 此除不用restController,返回视图而非字符串,使用controller即可
@Controller
public class HelloController {
@GetMapping("/basic")
public String hello(Model model) {
// name
model.addAttribute("name", "hello freemarker");
// stu
Student student = new Student();
student.setAge(18);
student.setName("小明");
model.addAttribute("stu", student);
return "01-basic";
}
}
访问测试
如何替换的
加了依赖,相关类在启动时加到了spring容器,默认配置后缀ftlh(不过通常以ftl结尾),模板路径classpath/templates
指令语法
集合指令
增加接口
@GetMapping("/list")
public String list(Model model){
//------------------------------------
Student stu1 = new Student();
stu1.setName("A");
stu1.setAge(18);
stu1.setMoney(666f);
stu1.setBirthday(new Date());
//小红对象模型数据
Student stu2 = new Student();
stu2.setName("B");
stu2.setMoney(999f);
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.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 s>
<tr>
<td>${s_index+1}</td>
<td>${s.name}</td>
<td>${s.age}</td>
<td>${s.money}</td>
</tr>
</#list>
</table>
<hr>
<#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:<br/>${stuMap.stu1.name}
年龄:<br/>${stuMap.stu1.age}
<br/>
<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:<br/>${stuMap["stu2"].name}
年龄:<br/>${stuMap["stu2"].age}
<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>
<hr>
</body>
</html>
<#list stus as s>遍历
子标签 ${s.name}获取值
获取索引值为s_index也可以+1处理
map值获取用stu.[“学生1”].age
或者用stu.学生1.age
遍历则使用kes as key
然后key为每个对象,用上述方法即可
if指令
<#if s.name=="A">
<tr style="color: red">
<td>${s_index+1}</td>
<td>${s.name}</td>
<td>${s.age}</td>
<td>${s.money}</td>
</tr>
<#else>
<tr>
<td>${s_index+1}</td>
<td>${s.name}</td>
<td>${s.age}</td>
<td>${s.money}</td>
</tr>
</#if>
=与==号用法一致
运算符
gt代替> 可能会被当作结束字符<#if (x>y)>
空值处理
防止报错,代码健壮性
外层嵌套
<#if stus??>
</#if>
变量空值处理
${name!""}
加了个!“”
也可以在""里加入你想要填入的默认值
${name!"============"}
内建函数
用法
集合大小
${变量+?+函数名称}
日期格式化+去除每三位生成的一逗号
${today?datetime}
<br>
${today?string("yyyy年MM月")}
<br>
${score?c}
model.addAttribute("today", new Date());
model.addAttribute("score", 8823333333333333f);
json转为字符串
静态文件生成
配置文件增加引用路径
template-loader-path: classpath:/templates #模板存放位置
新建测试类,引入freemarker上下文
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 {
// 1.获取模板对象通过文件名搭配配置文件寻找
Template template = configuration.getTemplate("02-list.ftl");
// 2.使用该对象合成模型
// 参数1为模板文件。2为输出流,指定生成文件的位置
template.process(getData(), new FileWriter("d:/02-list.html"));
}
private Map getData() {
//------------------------------------
Map<String, Object> map = new HashMap<>();
Student stu1 = new Student();
stu1.setName("A");
stu1.setAge(18);
stu1.setMoney(666f);
stu1.setBirthday(new Date());
// 小红对象模型数据
Student stu2 = new Student();
stu2.setName("B");
stu2.setMoney(999f);
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("score", 8823333333333333f);
return map;
}
}
运行生成模板文件
MinIO
对象存储方式对比
服务器,爆内存要扩麻烦
分布式,构建复杂
第三方,贵,裤兜子里没钢镚买不起
分布式文件系统
minIO nb!
概述
开源,保存大空间
单行命令运行
支持云存储迁移接口,可控大小
环境搭建
拉取镜像
docker pull minio/minio
创建容器
docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data
开机启动
用户名minio 密码minio123
访问地址 http://192.168.233.136:9000
如果是自己下的镜像那么就会出现上述错误
黑马给我们的古老的minio环境变量产生了错误
修改为MINIO_ROOT_USER MINIO_ROOT_PASSWORD且以下面这条代码启动
-
映射了内部端口的9001和9000 9000为api,9001为控制台
-
控制台端口–console-address “:9001” 必须加不加访问不了
在 MinIO 中,
--console-address ":9001"
参数用于指定控制台的访问地址和端口。如果不指定这个参数,控制台将不会自动启动,或可能使用默认设置,导致无法访问。主要原因:
- 控制台未启用:
- 默认情况下,MinIO 可能不会启动控制台,除非明确指定其地址和端口。
docker run -p 9000:9000 \-p 9001:9001 --name minio -d --restart=always -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data
--console-address ":9001"
基本概念
bucket 图片根目录文件夹
object html文件等等…
keys 文件名
点+建桶 由于黑马的镜像太大 ,自带虚拟机和镜像后续操作版本不和黑马的minio操作一致,不过原理差不多
快速入门
上传list.html到minio 并且访问
创建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>
引导类
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);
}
}
上传业务类
package com.heima.minio;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import javax.imageio.stream.FileImageInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class MinIOTest {
public static void main(String[] args) {
//获取文件流,后续传输到服务器
try {
FileInputStream inputStream = new FileInputStream("D:\\02-list.html");
// 1 创建minio客户端输入密码账号
MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.233.139:9000").build();
// 2 上传文件
// 构建上传配置参数
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object("list.html")//文件名
.contentType("text/html")//文件类型
.stream(inputStream, inputStream.available(), -1)//文件流
.bucket("leadnews")//桶名
.build();
// 放入客户端
minioClient.putObject(putObjectArgs);
// 成功则打印访问地址
System.out.println("http://192.168.233.139:9000/leadnews/list.html");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
访问连接报错,提示拒绝访问access denied
最新版得设置access权限
访问成功
封装Minio为starter
不需要再每一个微服务里集成,做成一个公共的微服务
步骤
导入公共微服务,文件管理starter
该微服务导入文件管理依赖,添加配置文件,注入service后续调用
父工程添加pom将其纳入子模块
heima-leadnews-basic
然后刷新maven 成功变蓝
项目集成
minio里依赖引入file服务
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-file-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
minio-test配置文件
application.yml
minio:
accessKey: minio
secretKey: minio123
bucket: leadnews
endpoint: http://192.168.233.136:9000
readPath: http://192.168.233.136:9000
一一对应(prefix标识读取在配置文件里minio的值)
注入FIlestorageService调用测试
package com.heima.minio;
import com.heima.file.service.FileStorageService;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
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):
// 指定了Spring Boot的启动类,用于加载Spring上下文。
// @RunWith(SpringRunner.class):指定了JUnit的运行器,用于在Spring环境中运行测试
@SpringBootTest(classes = MinIOApplication.class)
@RunWith(SpringRunner.class)
public class MinIOTest {
@Autowired
private FileStorageService fileStorageService;
@Test
public void test() throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream("D:\\02-list.html");
String path = fileStorageService.uploadHtmlFile("", "list.html", inputStream);
System.out.println(path);
}
// public static void main(String[] args) {
// //获取文件流,后续传输到服务器
// try {
// FileInputStream inputStream = new FileInputStream("D:\\02-list.html");
// // 1 创建minio客户端输入密码账号
// MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.233.136:9000").build();
// // 2 上传文件
// // 构建上传配置参数
// PutObjectArgs putObjectArgs = PutObjectArgs.builder()
// .object("list.html")//文件名
// .contentType("text/html")//文件类型
// .stream(inputStream, inputStream.available(), -1)//文件流
// .bucket("leadnews")//桶名
// .build();
// // 放入客户端
// minioClient.putObject(putObjectArgs);
// // 成功则打印访问地址
// System.out.println("http://192.168.233.136:9000/leadnews/list.html");
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//
// }
}
这种方法上传的会产生时间目录
回顾一下
ftl技术 存入minio文件系统,
并存url路径
用户访问该html文件
静态页面生成上传
mapper&&mapperxml
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> {
}
article服务添加freemarker依赖和file-starter
<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>
将minio-demo的配置文件迁移到article服务
minio:
accessKey: minio
secretKey: minio123
bucket: leadnews
endpoint: http://192.168.233.136:9000
readPath: http://192.168.233.136:9000
创建模板文件
article.ftl
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
<title>黑马头条</title>
<!-- 引入样式文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/lib/index.css">
<!-- 页面样式 -->
<link rel="stylesheet" href="../../../plugins/css/index.css">
</head>
<body>
<div id="app">
<div class="article">
<van-row>
<van-col span="24" class="article-title" v-html="title"></van-col>
</van-row>
<van-row type="flex" align="center" class="article-header">
<van-col span="3">
<van-image round class="article-avatar" src="https://p3.pstatp.com/thumb/1480/7186611868"></van-image>
</van-col>
<van-col span="16">
<div v-html="authorName"></div>
<div>{{ publishTime | timestampToDateTime }}</div>
</van-col>
<van-col span="5">
<van-button round :icon="relation.isfollow ? '' : 'plus'" type="info" class="article-focus"
:text="relation.isfollow ? '取消关注' : '关注'" :loading="followLoading" @click="handleClickArticleFollow">
</van-button>
</van-col>
</van-row>
<van-row class="article-content">
<#if content??>
<#list content as item>
<#if item.type='text'>
<van-col span="24" class="article-text">${item.value}</van-col>
<#else>
<van-col span="24" class="article-image">
<van-image width="100%" src="${item.value}"></van-image>
</van-col>
</#if>
</#list>
</#if>
</van-row>
<van-row type="flex" justify="center" class="article-action">
<van-col>
<van-button round :icon="relation.islike ? 'good-job' : 'good-job-o'" class="article-like"
:loading="likeLoading" :text="relation.islike ? '取消赞' : '点赞'" @click="handleClickArticleLike"></van-button>
<van-button round :icon="relation.isunlike ? 'delete' : 'delete-o'" class="article-unlike"
:loading="unlikeLoading" @click="handleClickArticleUnlike">不喜欢</van-button>
</van-col>
</van-row>
<!-- 文章评论列表 -->
<van-list v-model="commentsLoading" :finished="commentsFinished" finished-text="没有更多了"
@load="onLoadArticleComments">
<van-row id="#comment-view" type="flex" class="article-comment" v-for="(item, index) in comments" :key="index">
<van-col span="3">
<van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
</van-col>
<van-col span="21">
<van-row type="flex" align="center" justify="space-between">
<van-col class="comment-author" v-html="item.authorName"></van-col>
<van-col>
<van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
@click="handleClickCommentLike(item)">{{ item.likes || '' }}
</van-button>
</van-col>
</van-row>
<van-row>
<van-col class="comment-content" v-html="item.content"></van-col>
</van-row>
<van-row type="flex" align="center">
<van-col span="10" class="comment-time">
{{ item.createdTime | timestampToDateTime }}
</van-col>
<van-col span="3">
<van-button round size="normal" v-html="item.reply" @click="showCommentRepliesPopup(item.id)">回复 {{
item.reply || '' }}
</van-button>
</van-col>
</van-row>
</van-col>
</van-row>
</van-list>
</div>
<!-- 文章底部栏 -->
<van-row type="flex" justify="space-around" align="center" class="article-bottom-bar">
<van-col span="13">
<van-field v-model="commentValue" placeholder="写评论">
<template #button>
<van-button icon="back-top" @click="handleSaveComment"></van-button>
</template>
</van-field>
</van-col>
<van-col span="3">
<van-button icon="comment-o" @click="handleScrollIntoCommentView"></van-button>
</van-col>
<van-col span="3">
<van-button :icon="relation.iscollection ? 'star' : 'star-o'" :loading="collectionLoading"
@click="handleClickArticleCollection"></van-button>
</van-col>
<van-col span="3">
<van-button icon="share-o"></van-button>
</van-col>
</van-row>
<!-- 评论Popup 弹出层 -->
<van-popup v-model="showPopup" closeable position="bottom"
:style="{ width: '750px', height: '60%', left: '50%', 'margin-left': '-375px' }">
<!-- 评论回复列表 -->
<van-list v-model="commentRepliesLoading" :finished="commentRepliesFinished" finished-text="没有更多了"
@load="onLoadCommentReplies">
<van-row id="#comment-reply-view" type="flex" class="article-comment-reply"
v-for="(item, index) in commentReplies" :key="index">
<van-col span="3">
<van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
</van-col>
<van-col span="21">
<van-row type="flex" align="center" justify="space-between">
<van-col class="comment-author" v-html="item.authorName"></van-col>
<van-col>
<van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
@click="handleClickCommentReplyLike(item)">{{ item.likes || '' }}
</van-button>
</van-col>
</van-row>
<van-row>
<van-col class="comment-content" v-html="item.content"></van-col>
</van-row>
<van-row type="flex" align="center">
<!-- TODO: js计算时间差 -->
<van-col span="10" class="comment-time">
{{ item.createdTime | timestampToDateTime }}
</van-col>
</van-row>
</van-col>
</van-row>
</van-list>
<!-- 评论回复底部栏 -->
<van-row type="flex" justify="space-around" align="center" class="comment-reply-bottom-bar">
<van-col span="13">
<van-field v-model="commentReplyValue" placeholder="写评论">
<template #button>
<van-button icon="back-top" @click="handleSaveCommentReply"></van-button>
</template>
</van-field>
</van-col>
<van-col span="3">
<van-button icon="comment-o"></van-button>
</van-col>
<van-col span="3">
<van-button icon="star-o"></van-button>
</van-col>
<van-col span="3">
<van-button icon="share-o"></van-button>
</van-col>
</van-row>
</van-popup>
</div>
<!-- 引入 Vue 和 Vant 的 JS 文件 -->
<script src=" https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js">
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/vant.min.js"></script>
<!-- 引入 Axios 的 JS 文件 -->
<#--<script src="https://unpkg.com/axios/dist/axios.min.js"></script>-->
<script src="../../../plugins/js/axios.min.js"></script>
<!-- 页面逻辑 -->
<script src="../../../plugins/js/index.js"></script>
</body>
</html>
数据库content
类型 和 值 ,text文本类型,一种样式, img图片类型,另一种样式
上传基础css,js
tip:js(修改地址)
依次上传js和css文件
新建测试类
感叹一下,牛逼,只有当文章修改时需要重新查询数据库,日常的无数次给用户查询都访问静态页面,大大减轻了数据库的压力,一张表存longtext内容,一张表存基础信息
思路
-
查库获取content(次数很少,只有新增和修改会查)
-
不为空则通过模板文件生成html。获取模板
-
填充数据模型,由于模板里的list content名字固定,这里到时候的map属性值也的是content,且由于数据库存字符串,其值要求对象,json转对象
-
使用stringwrite (out)将html文件暂存
-
上传minio时将(out)参数传入,不过这里需要字节流,将字符流转为string再获取字节即可,第二个参数为文章id加html
-
返回的路径设置到article表的static url字段(articleService根据文章id)值为url
package test;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
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.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.apache.commons.lang3.StringUtils;
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.IOException;
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 {
// 注入template模板类
@Autowired
private Configuration configuration;
// 文件上传类
@Autowired
private FileStorageService fileStorageService;
@Autowired
private ApArticleContentMapper apArticleContentMapper;
@Autowired
private ApArticleMapper apArticleMapper;
@Test
public void createStaticUrlTest() throws Exception {
//1.获取文章内容
ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery()
.eq(ApArticleContent::getArticleId, 1383827787629252610L));
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);
}
}
}
标签:heima,org,private,2024,import,跟学,article,com,头条
From: https://blog.csdn.net/Zwwxd666/article/details/141915810