JAVA下单异步推送_异步实现服务器推送消息(聊天功能示例)

该博客介绍了使用JAVA实现异步服务器推送消息的技术,特别是在聊天功能的应用。通过示例项目展示了发送和接收消息的过程,包括离线消息处理和超时异常处理。项目包括了消息封装类、消息池、离线消息池、控制器和异常处理等关键组件。
摘要由CSDN通过智能技术生成

优点:异步推送消息只要客户端发送异步请求就可以,不依赖客户端版本,不存在浏览器兼容问题。

一、 主要讲解技术点,异步实现服务器推送消息

二、 项目示例,聊天会话功能,主要逻辑如下:

由Logan向 Charles 发送消息,如果Charles在线,则直接发送,否则存储为离线消息。

Charles 登录后向服务端发请求获取消息,首先查询离线消息,如果有消息直接返回。没有消息则等待。

由于长时间没有消息推送,等待会超时,所以设置超时异常通知,超时则返回空内容到客户端,由客户端再次发送获取消息请求,解决超时问题。

建议先复制项目到本地工程,边测试边理解。

项目示例如下:

1.   新建Maven项目 async-push

2.   pom.xml

http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

com.java

async-push

1.0.0

org.springframework.boot

spring-boot-starter-parent

2.0.5.RELEASE

org.springframework.boot

spring-boot-starter-web

org.springframework.cloud

spring-cloud-starter-oauth2

2.0.0.RELEASE

org.springframework

springloaded

1.2.8.RELEASE

provided

org.springframework.boot

spring-boot-devtools

provided

${project.artifactId}

org.apache.maven.plugins

maven-compiler-plugin

1.8

1.8

UTF-8

org.springframework.boot

spring-boot-maven-plugin

repackage

3.   AsyncPushStarter.java

packagecom.java;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;/*** 主启动类

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@SpringBootApplicationpublic classAsyncPushStarter {public static voidmain(String[] args) {

SpringApplication.run(AsyncPushStarter.class, args);

}

}

4.   SendMessageVo.java

packagecom.java.vo;/*** 发送消息封装体

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/

public classSendMessageVo {/*** 发送目标ID*/

privateString targetId;/*** 发送消息内容*/

privateString content;publicString getTargetId() {returntargetId;

}public voidsetTargetId(String targetId) {this.targetId =targetId;

}publicString getContent() {returncontent;

}public voidsetContent(String content) {this.content =content;

}

@OverridepublicString toString() {return "SendMessageVo [targetId=" + targetId + ", content=" + content + "]";

}

}

5.   PushMessageVo.java

packagecom.java.vo;importjava.util.Date;importcom.fasterxml.jackson.annotation.JsonFormat;/*** 推送消息封装体

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/

public classPushMessageVo {/*** 发送人ID,即消息来源*/

privateString srcId;/*** 发送消息内容*/

privateString content;/*** 发送时间*/@JsonFormat(pattern= "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")privateDate sendTime;publicString getSrcId() {returnsrcId;

}public voidsetSrcId(String srcId) {this.srcId =srcId;

}publicString getContent() {returncontent;

}public voidsetContent(String content) {this.content =content;

}publicDate getSendTime() {returnsendTime;

}public voidsetSendTime(Date sendTime) {this.sendTime =sendTime;

}

@OverridepublicString toString() {return "PushMessageVo [srcId=" + srcId + ", content=" + content + ", sendTime=" + sendTime + "]";

}

}

6.   MessagePool.java

packagecom.java.pool;importjava.util.HashMap;importjava.util.List;importjava.util.Map;importorg.springframework.stereotype.Component;importorg.springframework.web.context.request.async.DeferredResult;importcom.java.vo.PushMessageVo;/*** 消息池,存放所有消息

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@Componentpublic classMessagePool {private Map>> messagePool = new HashMap<>();public void put(String targetId, DeferredResult>result) {

messagePool.put(targetId, result);

}public DeferredResult>get(String targetId) {returnmessagePool.get(targetId);

}

}

7.   OfflineMessagePool.java

packagecom.java.pool;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjava.util.Map;importorg.springframework.stereotype.Component;importcom.java.vo.PushMessageVo;/*** 离线消息池

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@Componentpublic classOfflineMessagePool {private Map> offlineMessagePool = new HashMap<>();/*** 增加一条待发送消息

*

*@paramtargetId 发送目标ID

*@parammessage 推送消息体*/

public voidadd(String targetId, PushMessageVo message) {

List list =offlineMessagePool.get(targetId);if (null ==list) {

list= new ArrayList<>();

offlineMessagePool.put(targetId, list);

}

list.add(message);

}/*** 获取所有待发送消息

*

*@paramtargetId 发送目标ID

*@return发送目标对应的所有待发送消息*/

public Listget(String targetId) {

List list =offlineMessagePool.get(targetId);//如果存在,则移除后返回

if (null !=list) {

offlineMessagePool.remove(targetId);

}returnlist;

}

}

8.   MessageController.java

packagecom.java.controller;importjava.security.Principal;importjava.text.SimpleDateFormat;importjava.util.ArrayList;importjava.util.Date;importjava.util.HashMap;importjava.util.List;importjava.util.Map;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RestController;importorg.springframework.web.context.request.async.DeferredResult;importcom.java.pool.MessagePool;importcom.java.pool.OfflineMessagePool;importcom.java.vo.PushMessageVo;importcom.java.vo.SendMessageVo;/*** 发送接收消息接口类

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@RestControllerpublic classMessageController {private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@AutowiredprivateMessagePool messagePool;

@AutowiredprivateOfflineMessagePool offlineMessagePool;

@PostMapping("/sentMessage")public MapsentMessage(Principal principal, SendMessageVo sendMessage) {

PushMessageVo pushMessage= newPushMessageVo();

pushMessage.setSrcId(principal.getName());

pushMessage.setContent(sendMessage.getContent());

pushMessage.setSendTime(newDate());

System.out.println(sendMessage);

System.out.println(pushMessage);

DeferredResult> deferredResult =messagePool.get(sendMessage.getTargetId());//如果未上线,存到离线消息池中

if (null ==deferredResult) {

offlineMessagePool.add(sendMessage.getTargetId(), pushMessage);

}//直接推送消息给目标ID

else{

List list = new ArrayList<>();

list.add(pushMessage);

deferredResult.setResult(list);

}

Map result = new HashMap<>();

result.put("success", true);

result.put("sendTime", format.format(pushMessage.getSendTime()));returnresult;

}

@GetMapping("/getMessage")public DeferredResult>getMessage(Principal principal) {

DeferredResult> result = new DeferredResult<>();//先取出未推送的离线消息

List list =offlineMessagePool.get(principal.getName());//如果有离线消息,直接返回

if (null !=list) {

result.setResult(list);

}//否则等待接收新消息

else{

messagePool.put(principal.getName(), result);

}returnresult;

}

}

9.   ControllerExceptionHandler.java

packagecom.java.advice;importjava.util.ArrayList;importjava.util.List;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.ResponseBody;importorg.springframework.web.context.request.async.AsyncRequestTimeoutException;importcom.java.vo.PushMessageVo;/*** 捕获异步超时异常,并进行处理

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@ControllerAdvicepublic classControllerExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class);

@ResponseBody

@ExceptionHandler(AsyncRequestTimeoutException.class)public ListhandleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) {

logger.info("处理异步超时异常");//异步超时返回一个空集合,由前端继续发请求

List list = new ArrayList<>();returnlist;

}

}

下面是安全登录相关配置

10.   ApplicationContextConfig.java

packagecom.java.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;/*** 配置文件类

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@Configurationpublic classApplicationContextConfig {/*** 配置密码编码器,Spring Security 5.X必须配置,否则登录时报空指针异常*/@BeanpublicPasswordEncoder passwordEncoder() {return newBCryptPasswordEncoder();

}

}

11.   LoginConfig.java

packagecom.java.config;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/*** 登录相关配置

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@Configurationpublic class LoginConfig extendsWebSecurityConfigurerAdapter {

@Overrideprotected void configure(HttpSecurity http) throwsException {

http.authorizeRequests()//设置不需要授权的请求

.antMatchers("/js/*", "/login.html").permitAll()//其它任何请求都需要验证权限

.anyRequest().authenticated()//设置自定义表单登录页面

.and().formLogin().loginPage("/login.html")//设置登录验证请求地址为自定义登录页配置action ("/login/form")

.loginProcessingUrl("/login/form")//设置默认登录成功跳转页面

.defaultSuccessUrl("/main.html")//暂时停用csrf,否则会影响验证

.and().csrf().disable();

}

}

12.   SecurityUserDetailsService.java

packagecom.java.service;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.authority.AuthorityUtils;importorg.springframework.security.core.userdetails.User;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.stereotype.Component;/*** UserDetailsService实现类

*

*@authorLogan

* @createDate 2019-02-17

*@version1.0.0

**/@Componentpublic class SecurityUserDetailsService implementsUserDetailsService {

@AutowiredprivatePasswordEncoder passwordEncoder;

@Overridepublic UserDetails loadUserByUsername(String username) throwsUsernameNotFoundException {//数据库存储密码为加密后的密文(明文为123456)

String password = passwordEncoder.encode("123456");

System.out.println("username: " +username);

System.out.println("password: " +password);//模拟查询数据库,获取属于Admin和Normal角色的用户

User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("Admin,Normal"));returnuser;

}

}

13.     静态资源文件如下

static/login.html

static/main.html

static/js/jquery-3.3.1.min.js

4e63884c50bbcd3413368e9d71391576.png

14.   login.html

登录

用户自定义登录页面

登录框

用户名:
密码:

登录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值