详细讲解Vue+Java的websocket通讯,整合进微服务,XXL Job调用通讯流程

目录

1.后端:

1.1引入依赖

1.2.添加session会话管理WebSocketSessionHolder

1.3.添加建立session会话连接到会话管理及监听下线MapSessionWebSocketHandlerDecorator

1.4.添加断开session会话连接前请求处理UserAttributeHandshakeInterceptor

1.5.后端消息发送

1.6xxljob定时任务推送后展示的消息(同一用户同一科室不同电脑展示)

1.7引入XXLJOB流程

[1.7.1引入xxl job依赖](#1.7.1引入xxl job依赖)

1.7.2application.yml中添加

[1.7.3xxl job配置说明](#1.7.3xxl job配置说明)

2.前端:

2.1在你想要建立连接的页面created中添加连接

2.2websocket.js处理连接及断开重连

2.3接收到消息处理方法(这里不唯一根据自己业务逻辑来处理)


1.后端:

1.1引入依赖

bash 复制代码
<!-- 引入websocket依赖 -->
        <dependency>
          <groupId>com.pig4cloud.plugin</groupId>
          <artifactId>websocket-spring-boot-starter</artifactId>
          <version>${websocket-spring-boot-starter.version}</version>
        </dependency>

1.2.添加session会话管理WebSocketSessionHolder

java 复制代码
package com.pig4cloud.plugin.websocket.holder;

import org.springframework.web.socket.WebSocketSession;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public final class WebSocketSessionHolder {
    private static final Map<String, WebSocketSession> USER_SESSION_MAP = new ConcurrentHashMap();

    private WebSocketSessionHolder() {
    }

    public static void addSession(Object sessionKey, String sessionId, WebSocketSession session) {
        USER_SESSION_MAP.put(sessionKey.toString() + ":" + sessionId, session);
    }

    public static void removeSession(Object sessionKey, String sessionId) {
        USER_SESSION_MAP.remove(sessionKey.toString() + ":" + sessionId);
    }

    public static List<WebSocketSession> getSession(Object sessionKey) {
        return getMatchingKeys(USER_SESSION_MAP, sessionKey + ":");
    }

    public static Collection<WebSocketSession> getSessions() {
        return USER_SESSION_MAP.values();
    }

    public static Set<String> getSessionKeys() {
        return USER_SESSION_MAP.keySet();
    }

    /**
     * 获取匹配的keys *
     * 因当前系统允许多地同账号登录  所以需要兼容多地登录同账号都能接受到消息 *
     *
     * @param map     USER_SESSION_MAP
     * @param keyword addSession时是拼接的sessionKey + ":" + sessionId
     * @return List<WebSocketSession>
     */
    public static List<WebSocketSession> getMatchingKeys(Map<String, WebSocketSession> map, String keyword) {
        return map.keySet().stream()
                .filter(key -> key.contains(keyword)) // 你可以使用 contains, startsWith, endsWith 等
                .map(map::get).collect(Collectors.toList());
    }
}

map.keySet().stream()

.filter(key -> key.contains(keyword)) // 你可以使用 contains, startsWith, endsWith 等

.map(map::get).collect(Collectors.toList());

.map(map::get)用到了lmabda表达式的方法引用,就是在拿参数做操作

map::get相当于写法val -> map.get(val)

方法引用事例:

java 复制代码
package MethodReference02;
 
/*
    需求:
        1、定义一个接口(Printable):里面定义一个抽象方法:void printInt(int i);
        2、定义一个测试类(PrintableDemo),在测试类中提供两个方法
            一个方法是:usePrintable(Printable p)
            一个方法是主方法,在主方法中调用usePrintable方法
 */
public class PrintableDemo {
    public static void main(String[] args) {
        usePrintable((int i) -> {
            System.out.println(i);
        });
 
        usePrintable(i -> System.out.println(i));
 
        usePrintable(System.out::println);
    }
 
    private static void usePrintable(Printable p) {
        p.printInt(12345);
    }
}

1.3.添加建立session会话连接到会话管理及监听下线MapSessionWebSocketHandlerDecorator

java 复制代码
package com.pig4cloud.plugin.websocket.holder;


import com.tbyf.common.security.service.TokenService;
import com.tbyf.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Map;
import java.util.Objects;

/**
 * 系统允许一个账号多地登录 *
 * 为了适应同时登录都可以收到消息 重写添加sessionId *
 */
@Slf4j
public class MapSessionWebSocketHandlerDecorator extends WebSocketHandlerDecorator {

    @Autowired
    private TokenService tokenService;

    private final SessionKeyGenerator sessionKeyGenerator;

    public MapSessionWebSocketHandlerDecorator(WebSocketHandler delegate, SessionKeyGenerator sessionKeyGenerator) {
        super(delegate);
        this.sessionKeyGenerator = sessionKeyGenerator;
    }

    public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
        Object sessionKey = this.sessionKeyGenerator.sessionKey(session);
        WebSocketSessionHolder.addSession(sessionKey, session.getId(), session);
    }

    public void afterConnectionClosed(final WebSocketSession session, CloseStatus closeStatus) throws Exception {
        Map<String, String> queryParams = UriComponentsBuilder.fromUri(Objects.requireNonNull(session.getUri()))
                .build()
                .getQueryParams()
                .toSingleValueMap();
        LoginUser loginUser = tokenService.getLoginUser(queryParams);
        log.warn("用户名:{}下线了, webSocket token is {}", loginUser.getUsername(), loginUser.getToken());
        Object sessionKey = this.sessionKeyGenerator.sessionKey(session);
        WebSocketSessionHolder.removeSession(sessionKey, session.getId());
    }
}

1.4.添加断开session会话连接前请求处理UserAttributeHandshakeInterceptor

java 复制代码
package com.pig4cloud.plugin.websocket.custom;

import com.tbyf.common.core.exception.ServiceException;
import com.tbyf.common.security.service.TokenService;
import com.tbyf.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Map;

@Slf4j
public class UserAttributeHandshakeInterceptor implements HandshakeInterceptor {

    @Autowired
    private TokenService tokenService;

    public UserAttributeHandshakeInterceptor() {
    }

    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        // 获取请求的 URI 并解析查询参数
        Map<String, String> queryParams = UriComponentsBuilder.fromUri(request.getURI())
                .build()
                .getQueryParams()
                .toSingleValueMap();
        LoginUser loginUser = tokenService.getLoginUser(queryParams);
        if(loginUser == null){
            throw new ServiceException("登录状态已过期");
        }
        attributes.put("USER_KEY_ATTR_NAME", loginUser.getUsername());
        log.warn("用户名:{}上线了, webSocket token is {}", loginUser.getUsername(), loginUser.getToken());
        return true;
    }

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

1.5.后端消息发送

java 复制代码
package com.tbyf.system.controller;

import com.alibaba.fastjson2.JSONObject;
import com.pig4cloud.plugin.websocket.config.WebSocketMessageSender;
import com.pig4cloud.plugin.websocket.holder.WebSocketSessionHolder;
import com.tbyf.common.core.exception.ServiceException;
import com.tbyf.common.core.utils.StringUtils;
import com.tbyf.common.core.web.domain.AjaxResult;
import com.tbyf.common.security.annotation.InnerAuth;
import com.tbyf.system.api.model.LoginUser;
import com.tbyf.system.api.model.MessageModel;
import com.tbyf.system.service.ISysUserOnlineService;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Collection;
import java.util.List;

/**
 * <p>
 * 发送消息
 * </p>
 *
 * @author Lisy
 * @since 2020-05-29
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/msg")
public class MessageSendController {

    @Autowired
    private ISysUserOnlineService userOnlineService;

    /**
     * 通知消息
     */
    @InnerAuth
    @ApiOperation("通知消息")
    @PostMapping("/sendMsg")
    public AjaxResult sendMsg(@RequestBody MessageModel messageModel) {
        if (StringUtils.isEmpty(messageModel.getUserName())) {
            throw new ServiceException("用户名不能为空");
        }
        this.sendMsg(messageModel.getUserName(), JSONObject.toJSONString(messageModel));
        return AjaxResult.success(Boolean.TRUE);
    }

    /**
     * 通知消息
     */
    @InnerAuth
    @ApiOperation("向所有的session发送消息")
    @PostMapping("/sendAllSessionMsg")
    public AjaxResult sendAllSessionMsg() {
        String msg = "这是测试消息";
        Collection<WebSocketSession> sessions = WebSocketSessionHolder.getSessions();
        if (CollectionUtils.isEmpty(sessions)) {
            log.error("当前没有用户连接websocket");
            throw new ServiceException("当前没有用户连接websocket");
        }
        for (WebSocketSession session : sessions) {
            Object userName = session.getAttributes().get("USER_KEY_ATTR_NAME");
            if (session == null) {
                log.error("[send] session 为 null");
            } else if (!session.isOpen()) {
                log.error("[send] session 已经关闭");
            } else {
                try {
                    session.sendMessage(new TextMessage(msg));
                    log.error("[send] user({}) 发送消息({})成功", new Object[]{userName, msg});
                } catch (IOException var3) {
                    log.error("[send] user({}) 发送消息({}) 异常", new Object[]{userName, msg, var3});
                }
            }
        }
        return AjaxResult.success(Boolean.TRUE);
    }
    // (9.0使用的这个发送消息)
    private void sendMsg(String userName, String message) {

        boolean send = WebSocketMessageSender.send(userName, message);
        if (send) {
            log.error("发送消息成功!接收人:{}", userName);
        } else {
            log.error("发送消息失败!接收人:{}", userName);
        }
    }

    /**
     * 向所有符合科室的session发送消息(9.0使用的这个发送消息)
     */
    @InnerAuth
    @ApiOperation("向所有符合科室的session发送消息")
    @PostMapping("/sendAllDeptMsg")
    public AjaxResult sendAllDeptMsg(@RequestBody MessageModel messageModel) {
        if (StringUtils.isEmpty(messageModel.getOrgCode())) {
            throw new ServiceException("机构编码不能为空");
        }
        List<LoginUser> userList = userOnlineService.onlineUser(messageModel.getOrgCode());
        userList.stream().filter(user -> StringUtils.equals(user.getDeptCode(), messageModel.getDeptCode()))
                .forEach(user -> {
                    String userName = user.getUsername();
                    sendMsg(userName, JSONObject.toJSONString(messageModel));
                });
        return AjaxResult.success(Boolean.TRUE);
    }
}

// (9.0使用的这个发送消息)

private void sendMsg(String userName, String message) {

boolean send = WebSocketMessageSender.send(userName, message);

if (send) {

log.error("发送消息成功!接收人:{}", userName);

} else {

log.error("发送消息失败!接收人:{}", userName);

}

}
/**

* 向所有符合科室的session发送消息**(9.0使用的这个发送消息)**

*/

@InnerAuth

@ApiOperation("向所有符合科室的session发送消息")

@PostMapping("/sendAllDeptMsg")

public AjaxResult sendAllDeptMsg(@RequestBody MessageModel messageModel) {

if (StringUtils.isEmpty(messageModel.getOrgCode())) {

throw new ServiceException("机构编码不能为空");

}

List<LoginUser> userList = userOnlineService.onlineUser(messageModel.getOrgCode());

userList.stream().filter(user -> StringUtils.equals(user.getDeptCode(), messageModel.getDeptCode()))

.forEach(user -> {

String userName = user.getUsername();

sendMsg(userName, JSONObject.toJSONString(messageModel));

});

return AjaxResult.success(Boolean.TRUE);

}

}

java 复制代码
package com.pig4cloud.plugin.websocket.config;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.plugin.websocket.holder.WebSocketSessionHolder;
import com.pig4cloud.plugin.websocket.message.JsonWebSocketMessage;
import com.pig4cloud.plugin.websocket.util.SpringBeanContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class WebSocketMessageSender {
    private static final Logger log = LoggerFactory.getLogger(WebSocketMessageSender.class);

    public WebSocketMessageSender() {
    }

    public static void broadcast(String message) {
        Collection<WebSocketSession> sessions = WebSocketSessionHolder.getSessions();
        Iterator var2 = sessions.iterator();

        while (var2.hasNext()) {
            WebSocketSession session = (WebSocketSession) var2.next();
            send(session, message);
        }

    }
    // 9.0 使用的这个推送消息
    public static boolean send(Object sessionKey, String message) {
        List<WebSocketSession> sessions = WebSocketSessionHolder.getSession(sessionKey);
        if (CollectionUtils.isEmpty(sessions)) {
            log.info("[send] 当前 sessionKey:{} 对应 session 不在本服务中", sessionKey);
            return false;
        } else {
            sessions.forEach(session -> send(session, message));
            return Boolean.TRUE;
        }
    }

    public static void send(WebSocketSession session, JsonWebSocketMessage message) {
        ObjectMapper mapper = (ObjectMapper) SpringBeanContextHolder.getBean(ObjectMapper.class);

        try {
            send(session, mapper.writeValueAsString(message));
        } catch (JsonProcessingException var4) {
            throw new RuntimeException(var4);
        }
    }

    public static boolean send(WebSocketSession session, String message) {
        if (session == null) {
            log.error("[send] session 为 null");
            return false;
        } else if (!session.isOpen()) {
            log.error("[send] session 已经关闭");
            return false;
        } else {
            try {
                session.sendMessage(new TextMessage(message));
                return true;
            } catch (IOException var3) {
                log.error("[send] session({}) 发送消息({}) 异常", new Object[]{session, message, var3});
                return false;
            }
        }
    }
}

9.0使用的这个方法推送消息

复制代码
send(Object sessionKey, String message) {}
java 复制代码
package com.tbyf.system.controller;

import com.pig4cloud.plugin.websocket.config.WebSocketMessageSender;
import com.pig4cloud.plugin.websocket.holder.WebSocketSessionHolder;
import com.tbyf.common.core.exception.ServiceException;
import com.tbyf.common.core.web.domain.AjaxResult;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Collection;

/**
 * <p>
 * 发送任务消息
 * </p>
 *
 * @author Lisy
 * @since 2020-05-29
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/msg")
public class MessageSendController {


    /**
     * 通知消息
     */
    @ApiOperation("通知消息")
    @RequestMapping("/sendMsg")
    public AjaxResult sendMedicine() {
        this.sendMsg("SYS", "hello world");
        return AjaxResult.success();
    }

    /**
     * 通知消息
     */
    @ApiOperation("向所有的session发送消息")
    @PostMapping("/sendAllSessionMsg")
    public AjaxResult sendAllSessionMsg() {
        String msg = "这是测试消息";
        Collection<WebSocketSession> sessions = WebSocketSessionHolder.getSessions();
        if (CollectionUtils.isEmpty(sessions)) {
            log.error("当前没有用户连接websocket");
            throw new ServiceException("当前没有用户连接websocket");
        }
        for (WebSocketSession session : sessions) {
            Object userName = session.getAttributes().get("USER_KEY_ATTR_NAME");
            if (session == null) {
                log.error("[send] session 为 null");
            } else if (!session.isOpen()) {
                log.error("[send] session 已经关闭");
            } else {
                try {
                    session.sendMessage(new TextMessage(msg));
                    log.error("[send] user({}) 发送消息({})成功", new Object[]{userName, msg});
                } catch (IOException var3) {
                    log.error("[send] user({}) 发送消息({}) 异常", new Object[]{userName, msg, var3});
                }
            }
        }
        return AjaxResult.success();
    }

    private void sendMsg(String userName, String message) {

        boolean send = WebSocketMessageSender.send(userName, message);
        if (send) {
            log.error("发送消息成功!接收人:{}", userName);
        } else {
            log.error("发送消息失败!接收人:{}", userName);
        }
    }

}

流程:

1.启动时会调用

复制代码
MapSessionWebSocketHandlerDecorator的构造器

2.建立websocket连接时会调用

复制代码
MapSessionWebSocketHandlerDecorator.afterConnectionEstablished方法

存方到自定义的USER_SESSION_MAP集合中

复制代码
参数1:sessionKey 就是用户名,
复制代码
参数2:sessionId就是SESSION的id用于区分同一用户账号多地登录它的sessionKey 是一样的(比如同一个SYS账号多台电脑登录),用于同一用户同一科室但是不同电脑登录

参数3:session信息

模拟同一用户不同电脑同一科室登录

1.6xxljob定时任务推送后展示的消息(同一用户同一科室不同电脑展示)

1.7引入XXLJOB流程

1.7.1引入xxl job依赖
bash 复制代码
<!--xxl-job-->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>2.3.0</version>
        </dependency>
1.7.2application.yml中添加
bash 复制代码
xxl:
  job:
    admin:
      addresses: http://your ip:8080/xxl-job-admin/ #xxl job管理界面的ip
    executor:
      appname: his9-xxl-job #xxl job中执行器要配置的AppName,要与这里一致
      address:
      ip:
      port: 8998 
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30
    accessToken: default_token
1.7.3xxl job配置说明

2.前端:

2.1在你想要建立连接的页面created中添加连接

javascript 复制代码
created() {
    //连接Websocket
    var domain = window.location.host;
    //domain = '192.168.60.45:19100';
    var WSS_URL = `ws://${domain}/system/ws/info?access_token=${getToken()}`
    console.log("WSS_URL",WSS_URL)
    setURL(WSS_URL)
    //websocket连接失败导致node崩溃解决方案:
    //https://blog.csdn.net/qq_36577699/article/details/130559531
    createSocket()
    window.addEventListener('onmessageWS', this.handleMessage)
    this.$bus.$on("onmessageWS", this.handleMessage)

  }

2.2websocket.js处理连接及断开重连

javascript 复制代码
import {getToken} from '@/utils/auth'
import eventBus from '@/utils/eventBus.js'
let Socket = ''
var timer = null
var WSS_URL = null
// 建立连接
export function setURL(url) {
	WSS_URL = url
}
export function createSocket() {
	var domain = window.location.host;
	//console.log(WSS_URL)
	if (!Socket) {
		Socket = new WebSocket(WSS_URL)
		// const WebSocketProxy = new Proxy(WebSocket, {
		// 	construct: function(target, arg) {
		// 		try {
		// 			return new target(...arg)
		// 		} catch (error) {
		// 			return error
		// 		}
		// 	}
		// })
		// Socket = new WebSocketProxy(WSS_URL);
		Socket.onopen = onopenWS
		Socket.onmessage = onmessageWS
		Socket.onerror = onerrorWS
		Socket.onclose = oncloseWS
	} else {
		console.log('websocket已连接')
	}
}
// 打开WS之后发送心跳
export function onopenWS() {
	console.log('Websocket connect success')
	timer = setInterval(() => {
		try {
			Socket.send("/test");
		} catch (err) {
			console.log("断开了:" + err);
			createSocket();
		}
	}, 5 * 60 * 1000);


}
// 连接失败重连
export function onerrorWS(e) {
	console.log(e)
	Socket.close()
	// createSocket() //重连
}
// WS数据接收统一处理
export function onmessageWS(message) {
	eventBus.$emit('onmessageWS', message)
}
/**发送数据
 1. @param eventType
 */
export function sendWSPush(eventTypeArr) {
	const obj = {
		appId: 'airShip',
		cover: 0,
		event: eventTypeArr
	}
	if (Socket !== null && Socket.readyState === 3) {
		Socket.close()
		createSocket() //重连
	} else if (Socket.readyState === 1) {
		Socket.send(JSON.stringify(obj))
	} else if (Socket.readyState === 0) {
		setTimeout(() => {
			Socket.send(JSON.stringify(obj))
		}, 3000)
	}
}
export function oncloseWS(e) {
	clearInterval(timer)
	console.log('websocket已断开', e)
}

2.3接收到消息处理方法(这里不唯一根据自己业务逻辑来处理)

javascript 复制代码
handleMessage(message) {
      var deptCode = this.$store.state.user.deptCode
      var orgCode = this.$store.state.user.orgCode
      var userName = this.$store.state.user.userInfo.userName
      try {
        let info = JSON.parse(message.data)
        let infoList = JSON.parse(info.msg)
        //消息面向机构进行筛选
        if (info.orgCode != orgCode) return
        //消息面向科室进行筛选
        if (info.type == 'dept') {
          if (info.deptCode == deptCode) {
            if (info.windowType == 'nurse') this.nurerDeptList = infoList
            if (info.windowType == 'doctor') {
              this.doctorDeptList = infoList.map(item => {
                return {
                  ...item,
                  TYPE: 'dept'
                }
              })
            }
          }
        }
        //消息面向个人进行筛选
        else if (info.type == 'ry') {
          if (info.userName == userName) {
            if (info.windowType == 'nurse') this.nurseUserList = infoList
            if (info.windowType == 'doctor') {
              this.doctorUserList = infoList.map(item => {
                return {
                  ...item,
                  TYPE: 'ry'
                }
              })
            }
          }
        }

        if (info.windowType == 'nurse') {
          // console.log(this.nurerDeptList)
          this.nurseList = this.nurseUserList.concat(this.nurerDeptList)
          // console.log(this.nurseList)
        } else if (info.windowType == 'doctor') {
          this.doctorList = this.doctorUserList.concat(this.doctorDeptList)
        }
      } catch (e) {
        // console.log('error:', e)
        // console.log(message)
      }
      // this.$nextTick(() => {
      //   document.querySelector('.black-red').style.animation = 'none';
      // })
    },
相关推荐
源码技术栈1 小时前
基于微服务springcloud云智慧工地物联网SaaS平台源码(监管大屏+移动APP)
物联网·spring cloud·微服务·app·源码·智慧工地·数据大屏
阿巴~阿巴~1 小时前
HTTP头部字段:高效通信的关键
服务器·网络·网络协议·http·http头部字段
wangmengxxw1 小时前
微服务-服务容错
微服务·云原生·架构·服务容错
拾忆,想起1 小时前
Dubbo灰度发布完全指南:从精准引流到全链路灰度
前端·微服务·架构·dubbo·safari
木易 士心1 小时前
EACCES: permission denied 的深度诊断与解决指南
网络协议·系统安全
network_tester9 小时前
IXIA XM2网络测试仪电源模块损坏维修方法详解
网络·网络协议·tcp/ip·http·https·信息与通信·信号处理
刘一说11 小时前
Nacos 权限控制详解:从开源版 v2.2+ 到企业级安全实践
spring boot·安全·spring cloud·微服务·nacos·架构·开源
量子物理学12 小时前
openssl自建CA并生成自签名SSL证书
网络·网络协议·ssl
boboo_2000_013 小时前
基于SpringBoot+Langchain4j的AI机票预订系统
spring cloud·微服务·云原生·langchain