任务调度Quartz、api文档管理Swagger、Websocket协议

QuartZ

概述

任务调度就是控制定时任务的执行,比如订单超时、注册验证码等等都可以使用任务调度实现

Quartz入门案例

  1. 导入依赖
<dependency>
 <groupId>org.quartz-scheduler</groupId>
 <artifactId>quartz</artifactId>
 <version>2.3.2</version>
</dependency>
  1. 创建一个类MyJob实现接口Job,这个类编写我们要实现的任务
public class MyJob implements Job {
    /**
     * 在这个方法里面编写任务逻辑
     * @param context 这个对象可以获取调用本任务的JobDetail和Trigger,可通过这两个对象来从外界获取参数
     * @throws JobExecutionException
     */
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //获取JobDetail传入的参数
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        System.out.println(jobDataMap.get("name"));
        System.out.println("hello");
    }
}
  1. 创建一个类MyScheduler,这个类来对我们要实现的任务进行调度
public class MyScheduler {
    public static void main(String[] args) throws SchedulerException {
        //构造JobDetail对象
        JobDetail jobDetail = JobBuilder
                .newJob(MyJob.class)//JobDetail绑定的任务类
                .withIdentity("myJob")//任务的标识符
                .usingJobData("name","张三")//传入参数
                .build();

        //构造一个触发器,该触发器将立即执行,间隔两秒执行一次,一直重复
        SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("myJob")
                .startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                .build();

        //使用调度工厂构造一个调度对象,该调度对象将JobDetail和trigger绑定并开启任务调度
        StdSchedulerFactory factory = new StdSchedulerFactory();
        Scheduler scheduler = factory.getScheduler();
        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();
    }
}

cron表达式

调度的构建有多种,比上方使用的SimpleScheduler更加灵活的的cron表达式的方式,这种方式可以灵活的实现个性化的任务调度

分布式任务调度

在分布式系统中,为了提高性能,任务调度在多台服务器上进行,存在内存中的任务调度就不好用了;于是要对调度规则进行持久化,在官网的源码压缩包中src\org\quartz\impl\jdbcjobstore目录下就有对应各种数据库的建表sql脚本文件,这里使用oracle进行示例

代码实现

  1. 建表语句
--
-- A hint submitted by a user: Oracle DB MUST be created as "shared" and the 
-- job_queue_processes parameter  must be greater than 2
-- However, these settings are pretty much standard after any
-- Oracle install, so most users need not worry about this.
--
-- Many other users (including the primary author of Quartz) have had success
-- runing in dedicated mode, so only consider the above as a hint ;-)
--

delete from qrtz_fired_triggers;
delete from qrtz_simple_triggers;
delete from qrtz_simprop_triggers;
delete from qrtz_cron_triggers;
delete from qrtz_blob_triggers;
delete from qrtz_triggers;
delete from qrtz_job_details;
delete from qrtz_calendars;
delete from qrtz_paused_trigger_grps;
delete from qrtz_locks;
delete from qrtz_scheduler_state;

drop table qrtz_calendars;
drop table qrtz_fired_triggers;
drop table qrtz_blob_triggers;
drop table qrtz_cron_triggers;
drop table qrtz_simple_triggers;
drop table qrtz_simprop_triggers;
drop table qrtz_triggers;
drop table qrtz_job_details;
drop table qrtz_paused_trigger_grps;
drop table qrtz_locks;
drop table qrtz_scheduler_state;


CREATE TABLE qrtz_job_details
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    JOB_NAME  VARCHAR2(200) NOT NULL,
    JOB_GROUP VARCHAR2(200) NOT NULL,
    DESCRIPTION VARCHAR2(250) NULL,
    JOB_CLASS_NAME   VARCHAR2(250) NOT NULL, 
    IS_DURABLE VARCHAR2(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR2(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR2(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR2(1) NOT NULL,
    JOB_DATA BLOB NULL,
    CONSTRAINT QRTZ_JOB_DETAILS_PK PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE qrtz_triggers
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    JOB_NAME  VARCHAR2(200) NOT NULL, 
    JOB_GROUP VARCHAR2(200) NOT NULL,
    DESCRIPTION VARCHAR2(250) NULL,
    NEXT_FIRE_TIME NUMBER(13) NULL,
    PREV_FIRE_TIME NUMBER(13) NULL,
    PRIORITY NUMBER(13) NULL,
    TRIGGER_STATE VARCHAR2(16) NOT NULL,
    TRIGGER_TYPE VARCHAR2(8) NOT NULL,
    START_TIME NUMBER(13) NOT NULL,
    END_TIME NUMBER(13) NULL,
    CALENDAR_NAME VARCHAR2(200) NULL,
    MISFIRE_INSTR NUMBER(2) NULL,
    JOB_DATA BLOB NULL,
    CONSTRAINT QRTZ_TRIGGERS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    CONSTRAINT QRTZ_TRIGGER_TO_JOBS_FK FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) 
      REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) 
);
CREATE TABLE qrtz_simple_triggers
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    REPEAT_COUNT NUMBER(7) NOT NULL,
    REPEAT_INTERVAL NUMBER(12) NOT NULL,
    TIMES_TRIGGERED NUMBER(10) NOT NULL,
    CONSTRAINT QRTZ_SIMPLE_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    CONSTRAINT QRTZ_SIMPLE_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
	REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_cron_triggers
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    CRON_EXPRESSION VARCHAR2(120) NOT NULL,
    TIME_ZONE_ID VARCHAR2(80),
    CONSTRAINT QRTZ_CRON_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    CONSTRAINT QRTZ_CRON_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
      REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_simprop_triggers
  (          
    SCHED_NAME VARCHAR2(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    STR_PROP_1 VARCHAR2(512) NULL,
    STR_PROP_2 VARCHAR2(512) NULL,
    STR_PROP_3 VARCHAR2(512) NULL,
    INT_PROP_1 NUMBER(10) NULL,
    INT_PROP_2 NUMBER(10) NULL,
    LONG_PROP_1 NUMBER(13) NULL,
    LONG_PROP_2 NUMBER(13) NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR2(1) NULL,
    BOOL_PROP_2 VARCHAR2(1) NULL,
    CONSTRAINT QRTZ_SIMPROP_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    CONSTRAINT QRTZ_SIMPROP_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
      REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_blob_triggers
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    CONSTRAINT QRTZ_BLOB_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    CONSTRAINT QRTZ_BLOB_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_calendars
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    CALENDAR_NAME  VARCHAR2(200) NOT NULL, 
    CALENDAR BLOB NOT NULL,
    CONSTRAINT QRTZ_CALENDARS_PK PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE qrtz_paused_trigger_grps
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR2(200) NOT NULL, 
    CONSTRAINT QRTZ_PAUSED_TRIG_GRPS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_fired_triggers 
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    ENTRY_ID VARCHAR2(95) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    INSTANCE_NAME VARCHAR2(200) NOT NULL,
    FIRED_TIME NUMBER(13) NOT NULL,
    SCHED_TIME NUMBER(13) NOT NULL,
    PRIORITY NUMBER(13) NOT NULL,
    STATE VARCHAR2(16) NOT NULL,
    JOB_NAME VARCHAR2(200) NULL,
    JOB_GROUP VARCHAR2(200) NULL,
    IS_NONCONCURRENT VARCHAR2(1) NULL,
    REQUESTS_RECOVERY VARCHAR2(1) NULL,
    CONSTRAINT QRTZ_FIRED_TRIGGER_PK PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE qrtz_scheduler_state 
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    INSTANCE_NAME VARCHAR2(200) NOT NULL,
    LAST_CHECKIN_TIME NUMBER(13) NOT NULL,
    CHECKIN_INTERVAL NUMBER(13) NOT NULL,
    CONSTRAINT QRTZ_SCHEDULER_STATE_PK PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE qrtz_locks
  (
    SCHED_NAME VARCHAR2(120) NOT NULL,
    LOCK_NAME  VARCHAR2(40) NOT NULL, 
    CONSTRAINT QRTZ_LOCKS_PK PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);

create index idx_qrtz_j_req_recovery on qrtz_job_details(SCHED_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_j_grp on qrtz_job_details(SCHED_NAME,JOB_GROUP);

create index idx_qrtz_t_j on qrtz_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_t_jg on qrtz_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_t_c on qrtz_triggers(SCHED_NAME,CALENDAR_NAME);
create index idx_qrtz_t_g on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP);
create index idx_qrtz_t_state on qrtz_triggers(SCHED_NAME,TRIGGER_STATE);
create index idx_qrtz_t_n_state on qrtz_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_n_g_state on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_next_fire_time on qrtz_triggers(SCHED_NAME,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st on qrtz_triggers(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME);
create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_ft_j_g on qrtz_fired_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_ft_jg on qrtz_fired_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_ft_t_g on qrtz_fired_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
create index idx_qrtz_ft_tg on qrtz_fired_triggers(SCHED_NAME,TRIGGER_GROUP);
  1. 导入ojdbc与c3p0连接池依赖,配置quartz属性文件
# 修改存储方式,默认是内存,改为JobStoreTX改为独立环境
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# 自定义数据源名称
org.quartz.jobStore.dataSource=myDS
# 数据库相关配置
org.quartz.dataSource.myDS.driver=oracle.jdbc.OracleDriver
org.quartz.dataSource.myDS.URL=jdbc:oracle:thin:@localhost:1521:XE
org.quartz.dataSource.myDS.user=c##laowa
org.quartz.dataSource.myDS.password=laowa
  1. 修改MyScheduler,因为持久化后若重启调度,由于数据库已有该Job,会报错,于是可以先查看数据库是否有该Job的调度,如果有则继承该Job继续运行,没有则创建(以标识符JobKey来判定)
public class MyScheduler2 {
    public static void main(String[] args) throws SchedulerException {
        StdSchedulerFactory factory = new StdSchedulerFactory();
        Scheduler scheduler = factory.getScheduler();
        //通过标识符查看数据库是否已经存在改调度
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(new JobKey("myJob"));
        if(triggers.size()>0){
            for (Trigger trigger:triggers){
                //判断触发器类型,符合则恢复原Job
                if(trigger instanceof CronTrigger||trigger instanceof SimpleTrigger){
                    scheduler.resumeJob(new JobKey("myJob"));
                }
            }
        }else{
            //如果不存在则新建一个JobDetail和Trigger
            JobDetail jobDetail = JobBuilder
                    .newJob(MyJob.class)
                    .withIdentity("myJob")
                    .usingJobData("name","张三")
                    .build();
            SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger()
                    .withIdentity("myJob")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                    .build();
            scheduler.scheduleJob(jobDetail,simpleTrigger);
        }
        scheduler.start();
    }
}

做了持久化之后,只要在数据库中有对应的数据存在,另外任务执行也会带着这个任务执行,要停止Job需要对数据库的数据删除

SpringBoot整合Quartz

  1. 添加依赖spring-boot-starter-quartz
  2. 入口类添加@EnableScheduling注解,启用调度
  3. 自定义任务方法,使用@Scheduled修饰方法进行调度,对简单的任务可以使用这个注解的方式,复杂的任务调度需要配置JobDetail和Trigger的Bean
@Component
public class MyJob2 {
    @Scheduled(cron = "*/5 * * * * *")
    public void test1(){
        System.out.println("hello");
    }
}

集群

  • 集群当前使用JDBC-Jobstore (JobStoreTX或JobStoreCMT(容器事务))和TerracottaJobStore。特性包括负载平衡和作业故障转移,通过设置isClustered为true来启用集群
  • 集群中的每个节点必须有一个唯一的instanceId,这个类似于分布式事务中的事务组,只需将“AUTO”作为该属性的值,会自动分配Id
  • 每个集群每次任务开始中只有一个节点会执行,如果任务有一个重复的触发器,它会随机在一个节点执行,负载均衡是随机分配的,但会倾向于一个比较空闲的节点

配置文件

spring:
  quartz:
    #持久化到数据库方式
    job-store-type: jdbc
    initialize-schema: embedded
    properties:
      org:
        quartz:
          dataSource:
            #数据库连接信息
            quartzDS:
            driver: oracle.jdbc.OracleDriver
            URL: jdbc:oracle:thin:@192.168.206.1:1521:XE
            user: c##laowa
            password: laowa
          scheduler:
            instanceName: MyScheduler #实例的名称
            instanceId: AUTO #实例的Id,自动分配
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_ #持久化表的前缀
            isClustered: true #是否开启集群
            clusterCheckinInterval: 3000 #检查节点的时间间隔,检查节点是否挂掉,如果挂掉需要接管该节点的任务,这里表示3秒钟检查一次
            useProperties: false
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true

Swagger

概述

OpenAPI

OpenAPI规范是针对于REST API的接口描述格式,一个OpenAPI文件包括:

  • 可用端点(/用户)和每个端点上的操作(GET /用户、POST /用户)
  • 操作参数每个操作的输入和输出
  • 身份验证方法
  • 联系方式,许可,使用条款和其他信息

Swagger

  • Swagger是一套围绕OpenAPI规范构建的开源工具,可以帮助设计、构建、记录和使用REST API
  • Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。边写代码边写文档

使用

  1. 导入依赖
		<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
  1. 配置Bean
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.demo.user.web"))
                .paths(PathSelectors.any())
                .build().apiInfo(new ApiInfoBuilder()
                    .title("spring整合swagger")
                    .description("spring整合swagger详细信息")
                    .version("9.0")
                    .contact(new Contact("laowa","blog.csdn.net","123@123.com"))
                    .build());
    }
}
  1. 在Controller使用注解为接口添加描述
@Controller
@RequestMapping("user")
@ResponseBody
@Api(tags = "用户管理相关接口")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("get/{id}")
    @ApiOperation("通过id获取用户信息")
    @ApiImplicitParam(name = "id",value = "用户ID",defaultValue = "0")
    public ResponseVO getById(@PathVariable Integer id){
        return ResponseVO.FORBIDDEN;
    }

    @Transactional
    @PostMapping("addOrder/{id}")
    @ApiOperation("添加用户拥有的订单数")
    @ApiImplicitParam(name = "id",value = "用户ID",defaultValue = "0")
    public ResponseVO addOrder(@PathVariable Integer id) throws Exception {

        userService.addOrder(id);
        if(id.equals(1)){
            throw new Exception();
        }
        return ResponseVO.SUCCESS;

    }
}

WebSocket

概述

WebSocket和HTTP一样,是一个网络通信协议,而且webSocket是基于HTTP的,相对于HTTP具有长连接、双向交互的特点;HTTP协议最大的特点是无状态,基于典型的请求响应模式,所以造成了一个缺陷:请求只能由客户端发起,针对这个缺陷,WebSocket就有了用武之地

应用场景

  • 网络聊天
  • 应用更新消息推送

网络聊天的简单实现(原生写法)

服务器端

  1. 导入依赖(springboot版本2.5.4)
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
  1. 配置Bean,注意注解@EnableWebSocket开启websocket
@SpringBootApplication
@EnableWebSocket
public class DemoWebsocketApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoWebsocketApplication.class, args);
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
  1. 服务端推送消息编码
public class MessageEncoder implements Encoder.Text<Message>{
    @Override
    public String encode(Message message) throws EncodeException {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(message);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return "";
    }

    @Override
    public void init(EndpointConfig endpointConfig) {

    }

    @Override
    public void destroy() {

    }


}
  1. WebSocket服务端
@ServerEndpoint(value = "/chat/{userId}",encoders = MessageEncoder.class) //服务器连接点,客户端连接地址
@Component
public class WSServer {
    //使用ConcurrentHashMap保证线程安全,这个map存储所有当前连接上的客户端的session,通过session向客户端发送消息
    private static ConcurrentHashMap<String,Session> sessions = new ConcurrentHashMap<>();
    //连接上的回调
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId){
        System.out.println("客户端上线");
        sessions.put(userId,session);
    }
    //服务端接收到消息的回调
    @OnMessage
    public void onMessage(String message) throws IOException, EncodeException {
        ObjectMapper objectMapper = new ObjectMapper();
        Message message1 = objectMapper.readValue(message,Message.class);
        Session receiver = sessions.get(message1.getReceiverId());
        receiver.getBasicRemote().sendObject(message1);//向客户端发送消息
    }
    //发生错误的回调
    @OnError
    public void onError(Throwable throwable){
        throwable.printStackTrace();
    }
    //连接关闭的回调
    @OnClose
    public void onClose(@PathParam("userId") String id){
        sessions.remove(id);
    }
}

客户端

客户端的回调和服务端的回调是一样的,可以在onerror回调上进行websocket的重连

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery.js"></script>
    <script type="text/javascript">
        $(function (){
            let ws;
            $("#connect").click(function (){
                //判断websocket是否支持
                if("WebSocket" in window){
                    ws = new WebSocket("ws://localhost:8088/chat/"+$("#senderId").val());
                    //连接上服务器的回调
                    ws.onopen = function (){
                        alert("已连接");
                    }
                    //收到消息的回调
                    ws.onmessage = function (message){
                        $("#showMsg").innerHTML=JSON.parse(message.data).content;
                    }
                }else{
                    alert("请更新浏览器");
                }
                $("#send").click(function (){
                    let msg={
                        "senderId":$("#senderId").val(),
                        "content":$("#editMsg").val(),
                        "receiverId":$("#receiverId").val()
                    }
                    if(ws==null){
                        alert("请连接");
                    }else{
                        ws.send(JSON.stringify(msg));
                    }

                })
            })

        })
    </script>
</head>
<body>
    <div id="showMsg" style="width: 500px;height: 200px;background-color: aqua"></div>
    <br>
    <textarea id="editMsg" style="width: 500px;height: 100px"></textarea>
    <br>
    <button id="connect">连接</button>
    <br>
    <button id="send">发送</button> 发送方id:<input type="text" id="senderId"/> 接收方id:<input type="text" id="receiverId"/>
</body>

</html>

服务端SpringBoot整合WebSocket

  1. 握手拦截器
/***
 * @author shaofan
 * @Description 该类用来处理连接前后的事情,类似onOpen和onClose
 * @Date 2021-9-14
 * @Time 16:55
 */
public class MyHandShakeInterceptor implements HandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        return false;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {

    }
}
  1. websocket处理器
public class MyHandler extends TextWebSocketHandler {
    private static ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();

    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {

    }

}
  1. 配置
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
                .addInterceptors(new MyHandShakeInterceptor());
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }
}

对于上方的集合sessions,可以存在redis中

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值