学成在线-课程发布

一、课程预览

1.freemarker模板引擎

image-20260316221825435

示例:

1
2
3
4
5
6
7
8
9
@GetMapping("/testfreemarker")
public ModelAndView test(){
ModelAndView modelAndView = new ModelAndView();
//设置模型数据
modelAndView.addObject("name","小明");
//设置模板名称
modelAndView.setViewName("test");
return modelAndView;
}

模板文件如下

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
Hello ${name}!
</body>
</html>

二、课程发布

image

1.分布式事务

本地事务是指

image-20260317192857041

分布式事务的出现

image-20260317192928096

分布式事务概念

1
分布式事务是指跨越多个网络节点、多个数据库的事务操作。它的挑战在于网络不可靠(比如脑裂、超时、丢包),所以我们必须要在 CAP 定理和 BASE 理论中做出妥协——通常是为了可用性(A)和分区容错性(P),去换取最终一致性。

2.CAP理论

image-20260317193948795

但是,这三份中只能选俩个

image-20260317214605624

3.CP 系统

image-20260317222257459

4.AP 系统

image-20260317222319193

1
2
3
4
5
6
7
分区发生时:

CP: [Client] --写--> [Leader] --等待多数确认--> 成功/超时报错
节点不可达 → 拒绝服务,保数据正确

AP: [Client] --写--> [任意节点] --立即返回成功--> 后台异步同步
节点不可达 → 继续服务,数据稍后收敛

三、课程发布任务实现

1.页面静态化

image-20260318152102812

为了解决高并发的难题

代码实现

  1. 引入依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
  1. 写模板文件 article.ftl
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<h1>${title}</h1>
<p>${content}</p>
<#list tags as tag>
<span>${tag}</span>
</#list>
</body>
</html>
  1. 静态化核心代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Service
public class StaticPageService {

@Autowired
private Configuration freemarkerConfig; // FreeMarker配置

public void generateHtml(String articleId) throws Exception {
// 1. 从数据库查数据
Map<String, Object> dataModel = new HashMap<>();
Article article = articleService.getById(articleId);
dataModel.put("title", article.getTitle());
dataModel.put("content", article.getContent());

// 2. 获取模板
Template template = freemarkerConfig.getTemplate("article.ftl");

// 3. 指定输出文件路径
String outputPath = "/data/html/article_" + articleId + ".html";
File htmlFile = new File(outputPath);

// 4. 渲染并写入磁盘
try (FileWriter writer = new FileWriter(htmlFile)) {
template.process(dataModel, writer);
}

System.out.println("静态页面生成成功: " + outputPath);
}
}

2.上传minio-OpenFeign

发送http请求

image-20260318170552972

这里我们用到了OpenFeign

1
2
3
4
1.config配置类
2.请求接口编写
3.启动类注解
4.配置类配置熔断层

3.搜索功能实现

Elestatic做索引管理,搜索功能

image-20260318205421716

4.canal数据同步

image-20260318210055344

四、全流程总结

image-20260318220114142

然后这一模块就搞定啦

1.阶段一:触发发布(SDK 实现)

前端点击”课程发布”,请求到内容管理服务,服务做两件事:

  1. 把课程信息写入课程发布表(存储发布状态)
  2. 消息表里插入一条待处理消息(这是可靠消息机制的起点)

2.阶段二:定时扫描任务(XXL-job 实现)

任务调度服务定时触发,内容管理服务扫描消息表,拿到待处理的发布任务,开始”执行任务”主逻辑(图中那个大矩形框)。


3.阶段三:执行任务主逻辑(三件事并行推进)

图中执行任务框里发出三条线,依次完成:

① 课程缓存 → Redis 把课程信息写入 Redis,供前端快速查询。

② 课程索引 → 搜索服务 → Elasticsearch(esstatic 索引实现) 将课程数据同步到搜索服务,最终写入 ES,实现课程搜索功能。

③ 上传课程详情静态页面 → 媒资管理服务 → MinIO(freemarker + minio 实现) 用 Freemarker 模板引擎把课程详情渲染成 HTML 静态文件,再通过 Feign 调用媒资管理服务,上传到 MinIO 对象存储。这就是你刚才修的那个 bug 所在的环节。


4.阶段四:清理消息(SDK 自动实现)

三件事全部完成后,内容管理服务把消息表里对应的记录删除,标志本次发布任务处理完毕。如果中途某步失败,消息不删除,下次定时任务会重试,保证最终一致性。


整体设计思路

1
2
3
4
5
6
7
发布请求 → 写消息表(持久化)

定时扫描 → 幂等执行三个同步任务

全部成功 → 删消息(任务完结)

任意失败 → 消息保留 → 下次重试

用消息表 + XXL-job 定时扫描代替直接同步调用,核心目的是解耦 + 保证可靠性——即使 Redis、ES 或 MinIO 某一刻不可用,任务也不会丢,等服务恢复后自动重试