设置网站模板青岛响应式网站建设

张小明 2026/1/3 8:56:54
设置网站模板,青岛响应式网站建设,建设电子商务网站的试卷,alexa全球网站排名分析使用 SSE 单向推送实现 系统通知功能说明#xff1a;本说明基于自己毕设项目中“系统通知模块 (Notification / SSE)”的实现#xff0c;重点讲清楚在前端从 **初始化环境 → 建立 SSE 连接 → 解析服务端事件 → 打印日志 ** 的完整技术链路#xff0c;至于收到信息如何处理…使用 SSE 单向推送实现 系统通知功能说明本说明基于自己毕设项目中“系统通知模块 (Notification / SSE)”的实现重点讲清楚在前端从 **初始化环境 → 建立 SSE 连接 → 解析服务端事件 → 打印日志 ** 的完整技术链路至于收到信息如何处理和具体项目有关。在我的毕设中又系统通知这个功能单向的告诉用户你的内容发布审核成功或者失败等等一系列的单方面的通知所有选型sse而不选择websorket接下来先给出后端实现在是前端实现后端实现 系统通知模块后端实现说明涉及的核心类NotificationController通知相关 HTTP 接口SSE 流、已读、未读数、分页列表。NotificationService通知的持久化、SSE 连接管理与推送实现。Notification/notification表通知实体与数据库表结构。NotificationType/NotificationStatus通知类型与状态枚举如PRIVATE_MESSAGE。一、建立 SSE 通知连接1. ControllerGET /api/notify/stream接口类NotificationController方法stream(RequestHeader(value Last-Event-ID, required false) String lastEventId)路径/api/notify/stream作用校验当前用户是否已登录从UserContext.getUserId()读取 userId。未登录时抛出401 UNAUTHORIZED。登录状态下调用notificationService.connect(userId, lastEventId)建立 SSE 连接。简要流程从UserContext获取当前用户 ID。若未登录抛出ResponseStatusException(HttpStatus.UNAUTHORIZED)。若已登录将userId与Last-Event-ID交给NotificationService.connect。返回 Spring 的SseEmitter对象由框架维持 SSE 链接。2. ServiceNotificationService.connect方法签名public SseEmitter connect(Long userId, String lastEventId)内部逻辑创建一个SseEmitter实例超时时间为SSE_TIMEOUT 30 * 60 * 1000L30 分钟。将该SseEmitter加入到emitterPool中emitterPool类型为MapLong, CopyOnWriteArrayListSseEmitter按userId维护用户当前所有 SSE 连接。为emitter注册回调onCompletion/onTimeout/onError时从emitterPool中移除当前连接。发送一次心跳事件heartbeat名称为heartbeat数据为ping。解析Last-Event-ID如果有合法的lastEventId解析为lastIdLong。若解析失败或为空则为null。调用resendPending(userId, lastId, emitter)补发历史通知最多 100 条。返回SseEmitter等待后续通知推送。3. SSE 连接断开/超时在connect方法中对SseEmitter注册了onCompletion连接正常完成时调用removeEmitter(userId, emitter)。onTimeout连接超时时调用removeEmitter(userId, emitter)。onError发送异常等错误时调用removeEmitter(userId, emitter)。removeEmitter会从emitterPool对应用户的列表中移除该SseEmitter防止内存泄漏与后续重复推送。二、通知的创建与推送1. 通用创建推送NotificationService.createAndDispatch方法签名publicNotificationcreateAndDispatch(LonguserId,NotificationTypetype,Stringtitle,Stringcontent,Stringpayload)调用方示例私信业务中的离线提醒ChatServiceImpl。管理员公告等其他业务模块。执行流程调用buildNotification(userId, type, title, content, payload)构造一个Notification实体userId接收通知的用户 ID。type通知类型如PRIVATE_MESSAGE、SYSTEM_ANNOUNCEMENT等。title通知标题/摘要。content通知正文或简述。payload扩展 JSON 字符串用于前端跳转或展示更多信息。status初始为UNREAD。createdAt/updatedAt为当前时间。通过notificationMapper.insert(notification)将通知写入notification表。调用dispatch(notification)将该通知推送给所有当前在线的 SSE 连接。返回持久化后的Notification对象。2. 批量发送createAndDispatchBatch方法签名publicvoidcreateAndDispatchBatch(ListLonguserIds,NotificationTypetype,Stringtitle,Stringcontent,Stringpayload)逻辑对userIds逐个调用createAndDispatch用于对多用户广播同一类型通知如系统公告或活动推送。3. 通知推送NotificationService.dispatch方法签名public void dispatch(Notification notification)执行流程从emitterPool中取出该通知对应userId的所有SseEmitter列表。若列表为空说明用户此时没有打开 SSE 通道方法直接返回仅数据库中保留通知记录后续建立连接时再补发。遍历每个SseEmitter调用emitter.send(SseEmitter.event().id(String.valueOf(notification.getId())).name(notification.getType()).data(notification))id使用通知 ID字符串形式用于客户端的Last-Event-ID与断点续传。name使用notification.getType()即NotificationType的枚举名如PRIVATE_MESSAGE。data整个Notification对象前端接收后可按NotificationVO解析。如果发送过程中抛出IOException记录日志并调用removeEmitter移除当前失效连接。4. 历史通知补发NotificationService.resendPending方法签名private void resendPending(Long userId, Long lastId, SseEmitter emitter)补发策略若lastId ! null补发ID 大于lastId的所有通知。若lastId null补发所有未读status UNREAD通知。均按 ID 升序排序LIMIT 1条只是补发过最新一条数据列表通过数据库查询。通知通过SseEmitter.event().id(...).name(...).data(...)发送与实时推送一致。三、通知的已读、未读与统计1. 标记已读POST /api/notify/readControllerNotificationController.markRead路径/api/notify/read请求体NotificationReadRequest包含notificationIds可选要标记为已读的通知 ID 列表。upToId可选将id upToId的通知全部标记为已读。控制器逻辑从UserContext获取当前用户 ID未登录返回Result.unauthorized(未登录)。调用notificationService.markAsRead(userId, notificationIds, upToId)执行更新。再调用notificationService.countUnread(userId)获取最新未读数量。返回Result.success(unread)。ServiceNotificationService.markAsRead方法签名public void markAsRead(Long userId, ListLong notificationIds, Long upToId)执行逻辑若notificationIds为空且upToId null直接返回不做更新。创建LambdaUpdateWrapperNotificationeq(Notification::getUserId, userId)只更新当前用户的通知。eq(Notification::getStatus, NotificationStatus.UNREAD.name())只处理未读通知。若notificationIds不为空in(Notification::getId, notificationIds)。若upToId不为nullle(Notification::getId, upToId)。set(Notification::getStatus, NotificationStatus.READ.name())。执行notificationMapper.update(null, wrapper)完成批量更新。2. 未读数量统计GET /api/notify/unread-countControllerNotificationController.unreadCount路径/api/notify/unread-count逻辑从UserContext获取当前用户 ID未登录返回Result.unauthorized(未登录)。调用notificationService.countUnread(userId)获取未读数量。返回Result.success(unread)。ServiceNotificationService.countUnread方法签名public long countUnread(Long userId)逻辑使用LambdaQueryWrapperNotificationeq(Notification::getUserId, userId)eq(Notification::getStatus, NotificationStatus.UNREAD.name())调用notificationMapper.selectCount(wrapper)返回未读总数。3. 最近未读列表GET /api/notify/recent路径/api/notify/recent说明用于调试或前端恢复获取当前用户最近未读通知最多 100 条。Service 方法public ListNotification recentUnread(Long userId)条件userId匹配当前用户status UNREAD排序createdAt倒序last(LIMIT 100)限制数量。四、通知分页列表接口1. 接口GET /api/notify/listControllerNotificationController.list路径/api/notify/list入参Querypage页码默认1。pageSize每页数量默认10。status可选UNREAD/READ/ALL默认ALL不传时也视为ALL。流程从UserContext获取当前用户 ID未登录返回Result.unauthorized(未登录)。调用notificationService.listNotifications(userId, page, pageSize, status)。返回Result.success(PageNotificationVO)分页结构与项目统一total/size/current/pages/records等。ServiceNotificationService.listNotifications方法签名public PageNotificationVO listNotifications(Long userId, int page, int pageSize, String status)实现思路构造PageNotification pageParam new Page(page, pageSize)。构造LambdaQueryWrapperNotification wrappereq(Notification::getUserId, userId)。若status非空且不是ALLeq(Notification::getStatus, status.toUpperCase())。orderByDesc(Notification::getCreatedAt)。notificationMapper.selectPage(pageParam, wrapper)得到notificationPage。手动构造PageNotificationVO voPage new Page(page, pageSize, notificationPage.getTotal())遍历notificationPage.getRecords()将每条记录映射为NotificationVO复制id/userId/type/title/content/payload/status/createdAt等字段。voPage.setRecords(records)。返回voPage。五、与私信模块的集成离线私信提醒1. 场景描述当用户 A 给用户 B 发送一对一私信时如果 B 当前通过 WebSocket 在线有chat:online:{userId}标记只依赖聊天模块的实时消息推送STOMP/topic/chat/{sessionId}。如果 B 不在线没有在线标记在聊天消息正常写入chat_message表的基础上额外为 B 创建一条PRIVATE_MESSAGE类型的系统通知写入notification表并通过 SSE 推送/补发。2. 关键实现ChatServiceImpl.sendMessage在创建ChatMessage并更新ChatSession.updatedAt后判断当前会话是否为单聊single.equalsIgnoreCase(session.getSessionType())。查询该会话中“对方成员” (ChatSessionMemberuserId ! 发送者) 获取targetUserId。根据RedisConstants.CHAT_ONLINE_PREFIX targetUserId从 Redis 中读取在线标记若存在值认为对方在线不额外生成通知。若不存在值认为对方离线进入通知创建逻辑。离线场景下查询发送者用户信息取昵称或用户名作为senderName。构造标题title 收到一条新的私信。构造预览preview content的前若干字符超长截断。构造payloadJSON 字符串包含sessionId、messageId、senderId、senderName供前端跳转使用。调用notificationService.createAndDispatch(targetUserId, NotificationType.PRIVATE_MESSAGE, title, preview, payload)将通知写入notification表。若 B 此时已经建立 SSE 连接会立刻收到一条PRIVATE_MESSAGE事件。若 B 之后才建立 SSE 连接会通过补发逻辑resendPending收到历史通知。前端实现 前端 SSE 通知实现说明从 0 到看到控制台日志一、相关文件总览vite.config.js开发环境代理配置/api→ 后端VITE_API_BASE_URL。src/main.js应用启动时初始化用户状态并根据登录态初始化通知 SSE。src/utils/notificationStream.jsSSE 连接和消息解析的核心逻辑基于 fetch ReadableStream自实现 EventSource。src/stores/notification.js通知相关 Pinia Store管理未读数量、通知列表和 SSE 状态。src/components/Header/index.vue顶部 Header展示通知铃铛和未读红点。src/pages/UserCenter/pages/Notifications/index.vue用户中心「消息通知」页面展示通知列表。二、环境与代理为什么开发环境不直接跨域2.1 Vite 代理配置vite.config.js中开发环境的核心代理配置server:{port:3003,host:true,cors:true,...(isDev{proxy:{/api:{target:env.VITE_API_BASE_URL||http://localhost:8080,changeOrigin:true,rewrite:(path)path.replace(/^\/api/,)}}})}关键点浏览器访问的是http://localhost:3003前端 dev server。任何以/api开头的请求都会被 Vite 转发到VITE_API_BASE_URL对应的后端例如浏览器/api/api/notify/streamVite rewrite去掉第一个/api转成/api/notify/stream后端实际收到http://127.0.0.1:8081/api/notify/stream和后端文档一致。对浏览器来说始终是同源 (3003)不会触发 CORS 校验跨域问题由 dev server 帮我们挡在背后。三、应用入口在什么时机建立 SSE 连接3.1src/main.js在main.js中创建应用实例并挂载 Pinia / Router / UI 库。初始化用户状态从 localStorage 恢复登录态constappcreateApp(App)app.use(pinia)app.use(router)// ...constuserStoreuseUserStore()constnotificationStoreuseNotificationStore()userStore.initUserState()当恢复登录状态后如果用户已登录立即初始化通知 SSE并获取一次未读数量if(userStore.isLogin){initNotificationStream()notificationStore.fetchUnreadCount()}同时监听登录状态变化自动处理连接的建立和关闭watch(()userStore.isLogin,(isLogin){if(isLogin){initNotificationStream()notificationStore.fetchUnreadCount()}else{closeNotificationStream()notificationStore.reset()}})结论应用启动时如果用户已登录 → 自动建立 SSE 连接。用户登录成功 → 自动建立连接。用户登出或 token 失效 → 自动关闭连接并清空通知状态。四、SSE 核心notificationStream.js中的连接与解析4.1 连接地址/api/api/notify/stream说明浏览器视角http://localhost:3003/api/api/notify/stream同源。Vite 代理去掉第一个/api→/api/notify/stream。后端视角http://127.0.0.1:8081/api/notify/stream与接口文档一致。4.2 建立 SSE 连接initNotificationStream负责发起连接exportfunctioninitNotificationStream(){if(typeofwindowundefined)returnif(reading)return// 已在读流则不重复建立constuserStoreuseUserStore()constnotificationStoreuseNotificationStore()try{constsseUrl/api/api/notify/streamconsole.log([SSE] 准备建立连接:,sseUrl)abortControllernewAbortController()readingtruenotificationStore.setSseConnected(true)fetch(sseUrl,{method:GET,headers:{Accept:text/event-stream,Cache-Control:no-cache,...(userStore.token?{Authorization:Bearer${userStore.token}}:{})},credentials:include,signal:abortController.signal}).then(/* 处理响应与读流 */).catch(/* 错误处理 */)}catch(error){// ...}}关键点使用原生fetch而不是EventSource是因为需要自定义Authorization头。通过Authorization: Bearer token携带登录状态兼容后端鉴权逻辑。credentials: include保留 Cookie 信息如果后端需要。4.3 处理 HTTP 响应并输出基础日志.then(async(response){console.log([SSE] 响应状态:,response.status,response.statusText)console.log([SSE] 响应头 content-type:,response.headers.get(content-type))if(!response.ok||!response.body){thrownewError(SSE 连接失败:${response.status})}constreaderresponse.body.getReader()constdecodernewTextDecoder(utf-8)letbuffer// 持续读流...})此时在浏览器控制台可以看到[SSE] 响应状态: 200 OK[SSE] 响应头 content-type: text/event-stream;charsetUTF-84.4 持续读取 SSE 流并解析事件核心循环逻辑while(true){const{done,value}awaitreader.read()if(done){console.log([SSE] 流已结束)break}bufferdecoder.decode(value,{stream:true})console.log([SSE] 收到原始 chunk:,buffer)// 根据 \n\n 切分事件块consteventsbuffer.split(\n\n)bufferevents.pop()||for(constrawEventofevents){constlinesrawEvent.split(\n)leteventTypemessageletdataletlastEventIdnullfor(constlineoflines){if(line.startsWith(event:)){eventTypeline.slice(6).trim()}elseif(line.startsWith(data:)){dataline.slice(5).trim()}elseif(line.startsWith(id:)){lastEventIdline.slice(3).trim()}}console.log([SSE] 解析到事件:,{eventType,lastEventId,data,rawEvent})if(lastEventId){notificationStore.setLastEventId(lastEventId)}if(eventTypeheartbeat)continueif(!data)continuetry{constparsedJSON.parse(data)notificationStore.handleIncomingNotification(parsed)}catch(error){console.error(解析通知 SSE 消息失败:,error,data)}}}控制台能看到的典型日志以 PRIVATE_MESSAGE 为例[SSE] 收到原始 chunk: id:21\nevent:PRIVATE_MESSAGE\ndata:{id:21,userId:3,type:PRIVATE_MESSAGE,...}\n\n[SSE] 解析到事件: { eventType: PRIVATE_MESSAGE, lastEventId: 21, data: {id:21,userId:3,type:PRIVATE_MESSAGE,...}, rawEvent: id:21\nevent:PRIVATE_MESSAGE\ndata:{id:21,...} }五、通知 Store如何消费和存储 SSE 消息文件src/stores/notification.js5.1 状态结构state:()({unreadCount:0,notifications:[],pagination:{total:0,size:10,current:1,pages:0},loadingList:false,loadingUnread:false,sseConnected:false,lastEventId:null})5.2 处理 SSE 推送的新通知handleIncomingNotification(notification){if(!notification||!notification.id){// 当前约定必须有 id 才认为是有效通知return}constexiststhis.notifications.some(itemitem.idnotification.id)if(!exists){this.notifications[notification,...this.notifications]}// 未显式标记为 READ 的都算未读if(!notification.status||notification.statusUNREAD){this.unreadCount1}}效果每一条从 SSE 收到的 JSON 通知包含id字段会被添加到notifications列表顶部。未读数量unreadCount会自增用于 Header 红点等 UI 展示。六、UI 展示从 Store 到页面/控制台6.1 Header 铃铛红点文件src/components/Header/index.vueconstnotificationStoreuseNotificationStore()consthasUnreadcomputed(()notificationStore.unreadCount0)在模板中button classgridButton clickhandleNotificationClick span styleposition: relative; display: inline-block; i classfas fa-bell text-lg/i span v-ifhasUnread classunreadBadge/span /span /button当 SSE 收到任意一条未读通知handleIncomingNotification→unreadCounthasUnread变为trueunreadBadge渲染小红点点亮。6.2 用户中心「消息通知」页面文件src/pages/UserCenter/pages/Notifications/index.vueconstnotificationStoreuseNotificationStore()constnotificationscomputed(()notificationStore.notifications)constloadingcomputed(()notificationStore.loadingList)首次进入页面时会从后端拉一次历史通知列表onMounted((){notificationStore.fetchNotifications({page:1,pageSize:10,status:ALL})})当 SSE 推送新通知时Store 的notifications首元素会是最新一条此页面会自动响应式更新用户可以点击查看并标记为已读。最后、小结建立连接/api/notify/stream→NotificationService.connect→ 维护emitterPool 心跳 历史补发。创建通知各业务如私信模块调用NotificationService.createAndDispatch将通知写入notification表并尝试通过 SSE 推送。接收与补发在线用户通过 SSE 立即收到通知离线用户下次连接时通过Last-Event-ID或未读筛选补发最多 100 条。已读/未读管理/api/notify/read和/api/notify/unread-count负责已读标记与未读统计。列表与分页/api/notify/recent提供最近未读调试接口/api/notify/list提供按状态过滤的分页通知列表。私信集成ChatServiceImpl在单聊离线场景下为接收方生成PRIVATE_MESSAGE通知实现“有人给你发私信”类型的系统提示。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

上海做网站比较好的网站百度收录删除

Sparta是一款强大的Python GUI应用程序,专门用于简化网络基础设施的渗透测试流程。这款工具由SECFORCE团队开发,通过直观的图形界面帮助安全测试人员在扫描和枚举阶段提高工作效率,让您能够更专注于分析结果而非繁琐的工具配置。 【免费下载链…

张小明 2025/12/27 11:57:57 网站建设

有做lol直播网站有哪些网页设计旅游模板图片

第一章:AI Agent权限管理的核心挑战在构建现代AI系统时,AI Agent的权限管理成为保障系统安全与合规运行的关键环节。随着Agent被赋予更复杂的任务执行能力,其访问资源、调用API、操作数据的权限范围不断扩大,传统的静态权限模型已…

张小明 2026/1/3 4:32:44 网站建设

贵州省清镇市建设学校网站常德seo技术

3.7 HTML表格标签 表格由 <table> 标签来定义。每个表格均有若干行&#xff08;由 <tr> 标签定义&#xff09;&#xff0c;每行被分割为若干单元格&#xff08;由 <td> 标签定义&#xff0c;即&#xff1a;table data&#xff1a;数据单元格的内容&#xff0…

张小明 2025/12/31 18:40:50 网站建设

学习网站的设置和网页的发布刷东西网站怎么做

在ARM Cortex嵌入式开发中&#xff0c;你是否经常面临这样的困境&#xff1a;信号处理算法在PC上运行良好&#xff0c;移植到嵌入式环境却性能急剧下降&#xff1f;实时性要求难以满足&#xff0c;内存占用超出预期&#xff1f;CMSIS-DSP正是为解决这些痛点而生的专业信号处理库…

张小明 2025/12/28 1:55:54 网站建设

做翻糖的网站哪几个做内贸的网站比较好一点

ELK&#xff08;现在通常称为 Elastic Stack&#xff0c;加入 Beats 后扩展为 ELKB&#xff09;在运维工作中使用非常广泛&#xff0c;是企业级日志管理、监控告警、故障排查的主流开源解决方案&#xff0c;尤其是在中大型互联网公司、云原生架构、分布式系统的运维场景中&…

张小明 2025/12/28 1:55:51 网站建设

国外网站大牛不懂英语可以做吗东莞网站seo优化

第一章&#xff1a;R 量子模拟的纠缠度计算在量子信息科学中&#xff0c;纠缠度是衡量量子系统非经典关联强度的核心指标。利用 R 语言进行量子态模拟与纠缠分析&#xff0c;能够为研究者提供灵活且可重复的数值实验环境。通过构建多粒子量子态向量并计算其约化密度矩阵&#x…

张小明 2025/12/28 1:55:48 网站建设