feat(msg): SSE 实时通知体系 (Phase 1-4 + 7 部分)

后端:
- MessageSseService (emitter 注册表, 25s 心跳, pushNewMessage 仅 log 不抛)
- BizMessageSseController (独立 Controller, 避开 /{msgId} 路径抢匹配)
- BizMessageMapper.countUnread SQL 修复 (跟 limit 解耦, SSE 推真值)
- BizMessageServiceImpl.insert/markRead 成功后 pushNewMessage 触发 SSE
- BizNotifyService.expertAuditResult (#1: 审核结果通知专家本人)
- BizExpertServiceImpl.updateByPrimaryKey 比对旧 audit_status, 真变化才发通知
- PlaceholderConfig (Spring 7 严格 placeholder 兜底, ignoreUnresolvable=true)

前端:
- utils/sseClient.js (@microsoft/fetch-event-source 封装, Bearer 鉴权, 单例连接)
- 全局事件总线 onGlobal: SSE new_message 扇出到业务页面 (切路由不掉)
- AdminLayout 全局订阅 + bell 角标 (el-badge 显示 unreadCount)
- userStore.expertAuditApproved: doctor 审核状态实时跟 DB
- AdminLayout doctor menu 加 requireAuditApproved 过滤 (未通过审核隐藏 3 menu)
- doctor/Home: 待参加会议/待签协议 2 pannel 按 audit 状态显示; load() 仅审核通过才拉数据
- doctor/Home + doctor/Messages: 点列表弹详情 dialog + markMessageRead 真已读
- AdminLayout SSE 触发时: 刷新 unread + 重新拉 audit 状态 (审核通过瞬间可见)

业务规则:
- 专家注册不群发 manager (manager 群体可能 100+, 每条注册发 N 条浪费)
- 通过审核无需重新登录 (SSE 链路实时联动)
This commit is contained in:
郭庆泰
2026-08-22 08:46:26 +08:00
parent c3eb8ed9c3
commit b6ac5b7d41
18 changed files with 697 additions and 39 deletions
@@ -0,0 +1,36 @@
package com.ruoyi.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
/**
* 全局宽松占位符解析器 — 2026-08-22 加.
*
* <p>Spring Framework 7 的 PlaceholderParser 改成严格模式:
* @Value("${prop}") 找不到就抛 PlaceholderResolutionException, 启动失败.
* 即便加 :default 也不兜底 (实测 guoju0808 的 xss.excludes / token.header 都炸).
*
* <p>修法: 全局注册一个 ignoreUnresolvablePlaceholders=true 的
* PropertySourcesPlaceholderConfigurer, 行为回退到 Spring 6 时代 — 找不到就
* 留作字面量 ${...}, 不抛. 各 @Value 调用方按需自取:
* <ul>
* <li>要么 yml 里确实配了 (现在的 FilterConfig / TokenService 都是这种情况)</li>
* <li>要么代码层用 Environment.getProperty(key, default) 自兜底 (更安全)</li>
* </ul>
*
* <p>bean name 必须叫 propertySourcesPlaceholderConfigurer, 覆盖 Spring Boot
* 自动注册的默认那个.
*/
@Configuration
public class PlaceholderConfig
{
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
PropertySourcesPlaceholderConfigurer c = new PropertySourcesPlaceholderConfigurer();
c.setIgnoreUnresolvablePlaceholders(true);
c.setIgnoreResourceNotFound(true);
return c;
}
}