24小时留言板

24小时留言板

核心效果如图所示

alt text

代码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# TodoController 代码解析

## 整体架构
这是一个基于Spring WebFlux的响应式控制器,结合Redis发布\订阅机制实现实时更新的待办事项系统。关键组件包括:
- **TodoRepo**:数据访问层接口
- **ReactiveRedisTemplate**:Redis响应式操作模板
- **ChannelTopic**:Redis消息通道
- **Sinks.Many**:Project Reactor的事件分发器

## 核心功能解析

### 1. Redis发布\订阅初始化
```java
public TodoController(TodoRepo repo, ReactiveRedisTemplate<String, String> redisTemplate) {
this.repo = repo;
this.redisTemplate = redisTemplate;

redisTemplate.listenTo(todoTopic)
.map(msg -> msg.getMessage())
.filter(message -> !StringUtils.isEmpty(message))
.subscribe(eventSink::tryEmitNext);
}
  • 功能:控制器启动时自动订阅Redis频道
  • 处理流程
    1. 监听todo_events频道
    2. 提取消息内容
    3. 过滤空消息
    4. 将消息推送到事件流(Sinks)

2. 事件广播机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void broadcastEvent(String eventType, Todo todo) {
String messageId = "global_" + todo.getId() + "_" + System.currentTimeMillis();
String payload = "";

\\ 事件类型处理
switch (eventType) {
case "create":
case "update":
payload = String.format("id:%s\nevent:%s\ndata:%s\n\n",
messageId, eventType, renderItem(todo));
break;
case "delete":
payload = String.format("id:%s\nevent:delete\ndata:%d\n\n",
messageId, todo.getId());
break;
}

\\ Redis发布
if (StringUtils.hasLength(payload)) {
redisTemplate.convertAndSend(todoTopic.getTopic(), payload).subscribe();
}
}
  • 事件格式:符合SSE规范,包含:
    • id:全局唯一事件ID(含时间戳)
    • event:事件类型(create\update\delete)
    • data:有效载荷
  • 安全设计:广播前检查消息有效性

3. SSE数据流接口

1
2
3
4
5
6
7
8
9
10
11
@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> list() {
Flux<String> initialFlux = repo.findAllByOrderByIdDesc()
.map(todo ->
String.format("id:init_%d\nevent:create\ndata:%s\n\n",
todo.getId(), renderItem(todo))
);

return Flux.merge(initialFlux, eventSink.asFlux())
.filter(payload -> !StringUtils.isEmpty(payload));
}
  • 实现策略
    1. 加载初始数据并转换为SSE格式(init前缀)
    2. 合并实时事件流(Redis订阅)
    3. 过滤空消息
  • SSE规范
    • id: [event_id]
    • event: [event_type]
    • data: [payload]
    • 消息以双换行结束

4. CRUD操作实现

创建待办事项

1
2
3
4
5
6
@PostMapping
public Mono<String> create(@ModelAttribute Form form) {
return repo.save(new Todo(null, HtmlUtils.htmlEscape(form.getContent())))
.doOnSuccess(todo -> broadcastEvent("create", todo))
.thenReturn("");
}
  • 安全特性:HTML内容转义防御XSS
  • 响应策略:空字符串实现PRG(Post-Redirect-Get)模式

删除待办事项

1
2
3
4
5
6
7
@PostMapping(path = "\{id}\delete", produces = MediaType.TEXT_HTML_VALUE)
public Mono<String> delete(@PathVariable Long id) {
return repo.findById(id)
.doOnSuccess(todo -> broadcastEvent("delete", todo))
.then(repo.deleteById(id))
.thenReturn("");
}
  • 关键流程
    1. 先查询再删除(确保广播正确的todo数据)
    2. 广播删除事件
    3. 执行删除操作

5. 视图渲染引擎

查看模式渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private String renderItem(Todo t) {
return """
<li id="todo-%d" class="message-item">
<div class="message-content">
%s
<div class="message-extra">
<div class="message-time"><i class="far fa-clock"><\i> %s<\div>
<\div>
<\div>
<div class="message-actions">
<button class="delete-btn" onclick="handleDelete(%d)">
<i class="fas fa-trash-alt"><\i>
<\button>
<\div>
<\li>
""";
}
  • 元素结构
    • 唯一ID绑定:todo-{id}
    • 内容显示区
    • 时间戳(当前服务器时间)
    • 删除按钮(绑定JavaScript操作)

编辑模式渲染(未使用)

1
2
3
private String renderItemEditing(Todo t) {
\\ 包含表单和按钮
}
  • 潜在扩展点:提供编辑功能时可复用

6. 安全机制

1
2
3
4
5
6
7
\\ 输入转义
HtmlUtils.htmlEscape(form.getContent())

\\ 输出转义
private String escapeHtml(String s) {
return HtmlUtils.htmlEscape(s);
}
  • 双保险策略:输入输出双重转义防御XSS攻击
  • 规范实践:使用Spring内置HtmlUtils工具类

设计亮点

  1. 响应式架构

    • Flux\Mono异步处理
    • 背压支持(onBackpressureBuffer)
  2. 实时广播系统

    • Redis发布\订阅
    • 多级事件分发(Sinks->Flux)
  3. 客户端驱动更新

    • HTML片段直接注入页面
    • 无JavaScript框架依赖
  4. 优雅降级策略

    • 事件ID前缀区分初始化\实时事件
    • 空消息过滤保证稳定性
  5. 扩展性设计

    • 事件类型支持(create\update\delete)
    • 预留编辑模式渲染器

优化建议

  1. 时间戳改进

    1
    2
    3
    4
    5
    \\ 当前使用服务器当前时间
    LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))

    \\ 建议:使用Todo创建时间
    t.getCreatedAt().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
  2. 事件完整性保证

    1
    2
    \\ 增加错误处理
    .doOnError(e -> log.error("Event broadcast failed", e))
  3. 集群支持增强

    1
    2
    3
    4
    5
    \\ 当前事件ID格式
    "global_" + todo.getId() + "_" + System.currentTimeMillis()

    \\ 建议增加实例标识
    "node1_" + todo.getId() + "_" + System.currentTimeMillis()
  4. 编辑功能集成
    可启用预留的renderItemEditing方法实现编辑功能

  5. 自动清理机制
    增加定时任务清理24小时前的待办事项

该系统实现了基于响应式编程和Redis发布\订阅的高效实时应用,兼顾了性能和安全,代码结构清晰且扩展性强,是实时应用开发的优秀范例。

1
2
##GITHUb仓库
https:\\github.com\qingyun201908\HTMXANDSpringWebflux

alt text