文章目录
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
访问成功