websocket通信
“WebSocket 允许客户端和服务器在连接建立后随时互相发送数据,而无需每次交互都重新建立连接。”我想请问,第一次前端往后端发送数据时,传递的数据应该满足接口的参数内容,在第一次建立连接后之后的数据传递还是要满足接口的参数吗,还是直接利用这个管道随意的传输数据就可以。
一、WebSocket 基础
WebSocket 是一种在客户端(如浏览器)和服务器之间建立持久化的全双工通信通道的协议。与传统的 HTTP 请求-响应模式不同,WebSocket 允许客户端和服务器在连接建立后随时互相发送数据,而无需每次交互都重新建立连接。这使得 WebSocket 特别适合实时应用,如聊天系统、实时通知、在线游戏等。
二、WebSocket 数据传输的基本原则
虽然 WebSocket 提供了高度的灵活性,允许在客户端和服务器之间发送任意类型的数据,但为了确保通信的可靠性和可维护性,遵循一定的数据结构和协议是非常重要的。以下是一些关键点:
- 一致的数据格式:
- JSON 格式:通常情况下,WebSocket 通信中使用 JSON 格式的数据,因为它易于解析和处理。
- 自定义协议:可以根据项目需求定义自定义的数据协议,以确保双方能够正确理解和处理消息。
- 消息类型和结构:
- 初始化消息:在连接建立时,前端可能需要发送初始化消息(如认证信息、会话参数等)。
- 业务消息:连接建立后,前端和服务器之间的消息应遵循预定义的结构,以便双方能够正确解析和处理。
- 错误处理:
- 定义明确的错误消息结构,便于前端和后端识别和处理错误情况。
- 版本控制:
- 如果项目发展需要,可以在消息中包含版本信息,以支持协议的升级和兼容性。
三、具体到你的项目
结合你提供的 api/v1/chat.py
和 chat/manager.py
代码,我们可以更具体地讨论 WebSocket 数据传输的流程和规范。
1. 初始连接和认证
在 api/v1/chat.py
中,定义了一个 WebSocket 端点 /chat/{flow_id}
。当前端通过这个端点建立连接时,后端会进行认证和流数据验证,然后将连接交给 ChatManager
进行处理。
前端示例代码:
const flow_id = "example_flow_id"; // 流程ID
const chat_id = "example_chat_id"; // 聊天ID
const token = "your_jwt_token"; // JWT 认证令牌// 建立 WebSocket 连接
const socket = new WebSocket(`ws://yourserver.com/api/v1/chat/${flow_id}?chat_id=${chat_id}&t=${token}`);// 连接成功
socket.onopen = function(event) {console.log("WebSocket 连接已建立");// 发送初始化消息socket.send(JSON.stringify({ "message": "Hello, server!", "flow_id": flow_id, "chat_id": chat_id }));
};// 接收消息
socket.onmessage = function(event) {const message = JSON.parse(event.data);console.log("收到消息:", message);// 在页面上显示消息
};// 连接关闭
socket.onclose = function(event) {console.log("WebSocket 连接已关闭");
};// 连接错误
socket.onerror = function(error) {console.error("WebSocket 错误:", error);
};
解释:
- 连接 URL:包含
flow_id
、chat_id
和 JWT 认证令牌t
,用于后端的认证和会话识别。 - 初始化消息:在连接建立后,前端发送一条包含
message
、flow_id
和chat_id
的 JSON 消息。
2. 后端处理连接和消息
在 chat/manager.py
中,ChatManager
类负责管理 WebSocket 连接和消息传递。handle_websocket
函数是核心处理函数,负责接收和处理前端发送的消息,并发送响应。
关键流程:
- 建立连接:
- 后端通过
await self.connect(flow_id, chat_id, websocket)
接受连接,并将其存储在active_connections
中。
- 后端通过
- 接收消息:
- 使用
await websocket.receive_json()
异步接收来自前端的 JSON 消息。
- 使用
- 处理消息:
- 调用
ChatClient.handle_message(payload)
处理接收到的消息。
- 调用
- 发送响应:
- 通过
send_json
或send_message
方法将处理结果发送回前端。
- 通过
关键函数示例:
async def handle_websocket(self,flow_id: str,chat_id: str,websocket: WebSocket,user_id: int,gragh_data: dict = None,
):await self.connect(flow_id, chat_id, websocket)try:while True:try:json_payload_receive = await asyncio.wait_for(websocket.receive_json(), timeout=2.0)except asyncio.TimeoutError:json_payload_receive = ''payload = json.loads(json_payload_receive) if json_payload_receive else {}await chat_client.handle_message(payload)except WebSocketDisconnect as e:logger.info('Client disconnected: {}'.format(e))except Exception as e:logger.exception(e)await self.close_client(client_key, code=status.WS_1011_INTERNAL_ERROR, reason='Internal Error')finally:await self.close_client(client_key, code=status.WS_1000_NORMAL_CLOSURE, reason='Client disconnected')
解释:
- 消息接收循环:持续接收前端发送的消息,并处理。
- 异常处理:捕获断开连接和其他异常,确保资源被正确释放。
- 资源清理:在
finally
块中,关闭连接并移除相关资源。
四、第一次发送与后续消息的区别
首次发送数据:
- 结构:通常需要包含认证信息或会话初始化参数,如
flow_id
、chat_id
和 JWT 认证令牌。 - 目的:用于建立会话、验证用户身份和初始化聊天上下文。
后续消息传递:
- 结构:应继续遵循预定义的消息结构,以确保前后端能够正确解析和处理数据。
- 灵活性:虽然 WebSocket 允许发送任意数据,但为了保持通信的一致性和可维护性,建议继续使用一致的数据格式和协议。
- 示例:
- 发送消息:前端发送包含
message
、flow_id
和chat_id
的 JSON 消息。 - 接收响应:后端处理消息后,发送结构化的响应,如
ChatMessage
对象的 JSON 表示。
- 发送消息:前端发送包含
为什么需要一致的数据结构:
- 可维护性:一致的数据结构使得前后端代码更易理解和维护。
- 错误减少:预定义的消息格式可以减少因数据不一致导致的解析错误。
- 扩展性:如果需要在未来添加新功能或字段,遵循一致的协议可以更容易地进行扩展。
五、具体示例解析
1. 前端发送初始化消息
socket.send(JSON.stringify({ "message": "Hello, server!", "flow_id": flow_id, "chat_id": chat_id
}));
message
:实际的聊天内容。flow_id
和chat_id
:用于标识会话和流,确保后端能够将消息正确路由到相应的处理逻辑。
2. 后端接收并处理消息
async def handle_websocket(self,flow_id: str,chat_id: str,websocket: WebSocket,user_id: int,gragh_data: dict = None,
):await self.connect(flow_id, chat_id, websocket)try:while True:try:json_payload_receive = await asyncio.wait_for(websocket.receive_json(), timeout=2.0)except asyncio.TimeoutError:json_payload_receive = ''payload = json.loads(json_payload_receive) if json_payload_receive else {}await chat_client.handle_message(payload)except WebSocketDisconnect as e:logger.info('Client disconnected: {}'.format(e))except Exception as e:logger.exception(e)await self.close_client(client_key, code=status.WS_1011_INTERNAL_ERROR, reason='Internal Error')finally:await self.close_client(client_key, code=status.WS_1000_NORMAL_CLOSURE, reason='Client disconnected')
- 接收消息:
websocket.receive_json()
接收前端发送的 JSON 消息,并解析为payload
。 - 处理消息:调用
chat_client.handle_message(payload)
处理消息逻辑。 - 发送响应:在
handle_message
中,使用send_json
或send_message
方法将响应发送回前端。
3. 后端发送响应
async def send_json(self, client_id: str, chat_id: str, message: ChatMessage, add=True):message.flow_id = client_idmessage.chat_id = chat_idwebsocket = self.active_connections[get_cache_key(client_id, chat_id)]if add:self.chat_history.add_message(client_id, chat_id, message)await websocket.send_json(message.dict())
- 设置消息字段:将
client_id
和chat_id
添加到消息中。 - 记录聊天历史:如果
add
为True
,将消息记录到聊天历史中。 - 发送消息:通过
websocket.send_json
将消息发送给前端。
目录
- WebSocket 基础概念
- 项目结构概览
- 前端建立 WebSocket 连接
- 后端 WebSocket 端点处理
- 后端服务层处理 WebSocket 消息
- 消息传递流程详细解析
- 断开连接和错误处理
- 总结与建议
1. WebSocket 基础概念
WebSocket 是一种在客户端(通常是浏览器)和服务器之间建立持久化的全双工通信通道的协议。与传统的 HTTP 请求-响应模式不同,WebSocket 允许客户端和服务器在连接建立后随时互相发送数据,而无需每次交互都重新建立连接。这使得 WebSocket 特别适合实时应用,如聊天系统、实时通知、游戏等。
FastAPI 是一个现代、高性能的 Web 框架,支持 WebSocket。它允许你轻松地定义和管理 WebSocket 端点,并与异步编程模型无缝集成。
2. 项目结构概览
根据你提供的项目结构和代码片段,WebSocket 相关的代码主要分布在两个文件中:
- API 层:
api/v1/chat.py
负责定义 WebSocket 端点,并处理来自前端的连接请求。 - 服务层:
chat/manager.py
负责管理 WebSocket 连接、处理消息、维护聊天历史等核心逻辑。
3. 前端建立 WebSocket 连接
在 WebSocket 通信流程中,前端首先需要建立与后端的 WebSocket 连接。假设前端使用 JavaScript,连接代码可能如下:
const flow_id = "example_flow_id"; // 流程ID
const chat_id = "example_chat_id"; // 聊天ID
const token = "your_jwt_token"; // JWT 认证令牌// 建立 WebSocket 连接
const socket = new WebSocket(`ws://yourserver.com/api/v1/chat/${flow_id}?chat_id=${chat_id}&t=${token}`);// 连接成功
socket.onopen = function(event) {console.log("WebSocket 连接已建立");// 可以发送初始化消息socket.send(JSON.stringify({ "message": "Hello, server!", "flow_id": flow_id, "chat_id": chat_id }));
};// 接收消息
socket.onmessage = function(event) {const message = JSON.parse(event.data);console.log("收到消息:", message);// 在页面上显示消息
};// 连接关闭
socket.onclose = function(event) {console.log("WebSocket 连接已关闭");
};// 连接错误
socket.onerror = function(error) {console.error("WebSocket 错误:", error);
};
解释:
- 建立连接:前端通过
WebSocket
构造函数指定后端的 WebSocket 端点(ws://yourserver.com/api/v1/chat/${flow_id}
),并附带必要的查询参数(如chat_id
和 JWT 认证令牌t
)。 - 事件处理:
onopen
:连接成功后触发,可以在此发送初始化消息。onmessage
:接收到后端发送的消息时触发,前端可以在此处理和显示消息。onclose
和onerror
:处理连接关闭和错误事件。
4. 后端 WebSocket 端点处理
在后端,api/v1/chat.py
定义了 WebSocket 端点 /chat/{flow_id}
,负责接收前端的连接请求,并将其传递给服务层进行进一步处理。
4.1 定义 WebSocket 端点
@router.websocket('/chat/{flow_id}')
async def chat(*,flow_id: str,websocket: WebSocket,t: Optional[str] = None,chat_id: Optional[str] = None,version_id: Optional[int] = None,Authorize: AuthJWT = Depends(),
):"""Websocket endpoint for chat."""try:# 认证if t:Authorize.jwt_required(auth_from='websocket', token=t)Authorize._token = telse:Authorize.jwt_required(auth_from='websocket', websocket=websocket)login_user = await get_login_user(Authorize)user_id = login_user.user_id# 获取流数据if chat_id:with session_getter() as session:db_flow = session.get(Flow, flow_id)if not db_flow:await websocket.accept()message = '该技能已被删除'await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason=message)if db_flow.status != 2:await websocket.accept()message = '当前技能未上线,无法直接对话'await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason=message)graph_data = db_flow.dataelse:flow_data_key = 'flow_data_' + flow_idif version_id:flow_data_key = flow_data_key + '_' + str(version_id)if not flow_data_store.exists(flow_data_key) or str(flow_data_store.hget(flow_data_key, 'status'),'utf-8') != BuildStatus.SUCCESS.value:await websocket.accept()message = '当前编译没通过'await websocket.close(code=status.WS_1013_TRY_AGAIN_LATER, reason=message)returngraph_data = json.loads(flow_data_store.hget(flow_data_key, 'graph_data'))if not chat_id:# 调试时,每次都初始化对象chat_manager.set_cache(get_cache_key(flow_id, chat_id), None)with logger.contextualize(trace_id=chat_id):logger.info('websocket_verify_ok begin=handle_websocket')await chat_manager.handle_websocket(flow_id,chat_id,websocket,user_id,gragh_data=graph_data)except WebSocketException as exc:logger.error(f'Websocket error: {str(exc)}')await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))except Exception as exc:logger.exception(f'Error in chat websocket: {str(exc)}')messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)if 'Could not validate credentials' in str(exc):await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason='Unauthorized')else:await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
解释:
- 端点定义:使用
@router.websocket('/chat/{flow_id}')
定义 WebSocket 端点,flow_id
是路径参数。 - 认证:
- 通过 JWT 令牌
t
或 WebSocket 的认证机制验证用户身份。 - 使用
Authorize.jwt_required
进行认证,确保只有合法用户可以建立连接。
- 通过 JWT 令牌
- 获取流数据:
- 如果
chat_id
存在,表示这是一个已有的聊天会话,后端从数据库中获取对应的Flow
数据。 - 如果
chat_id
不存在,则检查flow_data_store
(Redis)中是否存在编译通过的流数据。如果不存在或状态不为成功,关闭连接并返回错误。
- 如果
- 初始化聊天管理器:
- 如果没有
chat_id
,则调用chat_manager.set_cache
初始化聊天缓存。
- 如果没有
- 调用服务层处理 WebSocket:
- 使用
await chat_manager.handle_websocket(...)
将 WebSocket 连接交给服务层 (ChatManager
) 处理。
- 使用
- 异常处理:
- 捕获
WebSocketException
和其他异常,记录日志并适当地关闭 WebSocket 连接。
- 捕获
4.2 连接建立逻辑
当前端发起 WebSocket 连接时,后端会执行以下步骤:
- 认证用户:确保连接请求来自合法用户。
- 验证流数据:检查
flow_id
是否有效,确保相关技能已上线。 - 初始化连接:将连接添加到
ChatManager
的活动连接列表中。 - 转发处理:将 WebSocket 连接的后续消息处理交给服务层。
5. 后端服务层处理 WebSocket 消息
服务层代码 chat/manager.py
中的 ChatManager
类负责具体的 WebSocket 消息处理和连接管理。我们将重点关注与 WebSocket 通信相关的部分,特别是 handle_websocket
函数。
5.1 ChatManager
类概览
class ChatManager:def __init__(self):self.active_connections: Dict[str, WebSocket] = {}self.chat_history = ChatHistory()self.cache_manager = cache_managerself.cache_manager.attach(self.update)self.in_memory_cache = InMemoryCache()self.task_manager: List[asyncio.Task] = []self.active_clients: Dict[str, ChatClient] = {}self.stream_queue: Dict[str, Queue] = {}
解释:
active_connections
:存储当前活跃的 WebSocket 连接,键为client_id
和chat_id
的组合键。chat_history
:维护聊天历史记录。cache_manager
和in_memory_cache
:管理缓存数据,提升性能。active_clients
:存储已连接的客户端信息。stream_queue
:用于处理流式消息的队列。
5.2 handle_websocket
函数详解
async def handle_websocket(self,flow_id: str,chat_id: str,websocket: WebSocket,user_id: int,gragh_data: dict = None,
):client_key = uuid.uuid4().hexchat_client = ChatClient(...)await self.accept_client(client_key, chat_client, websocket)try:while True:try:json_payload_receive = await asyncio.wait_for(websocket.receive_json(), timeout=2.0)except asyncio.TimeoutError:json_payload_receive = ''payload = json.loads(json_payload_receive) if json_payload_receive else {}await chat_client.handle_message(payload)except WebSocketDisconnect as e:logger.info('Client disconnected: {}'.format(e))except Exception as e:logger.exception(e)await self.close_client(client_key, code=status.WS_1011_INTERNAL_ERROR, reason='Internal Error')finally:await self.close_client(client_key, code=status.WS_1000_NORMAL_CLOSURE, reason='Client disconnected')
解释:
- 接受客户端连接:
- 生成唯一的
client_key
。 - 创建
ChatClient
实例,代表一个具体的客户端。 - 调用
accept_client
方法将客户端连接加入active_clients
。
- 生成唯一的
- 消息接收循环:
- 使用
while True
进入循环,持续接收来自客户端的消息。 - 使用
asyncio.wait_for
设置接收消息的超时时间为 2 秒,避免长时间阻塞。 - 如果接收到消息,解析为
payload
并调用chat_client.handle_message(payload)
处理消息。
- 使用
- 异常处理:
WebSocketDisconnect
:客户端断开连接时,记录日志。- 其他异常:捕获并记录异常,关闭连接。
- 资源清理:
- 在
finally
块中,确保无论发生何种情况,连接都能被正确关闭,并释放相关资源。
- 在
6. 消息传递流程详细解析
为了更清晰地理解前端与后端之间的 WebSocket 通信流程,下面将结合前面介绍的两个文件中的关键代码,逐步解析消息从前端到后端再返回前端的完整流程。
6.1 前端发送消息
假设前端通过 WebSocket 连接后,发送一条消息:
socket.send(JSON.stringify({ "message": "Hello, server!", "flow_id": "flow_id", "chat_id": "chat_id" }));
解释:
- 消息内容:包含
message
(实际内容)、flow_id
和chat_id
。 - 发送方式:通过 WebSocket 的
send
方法发送 JSON 格式的消息。
6.2 后端接收消息
- API 层接收连接请求:
- 前端连接到
/chat/{flow_id}
,后端chat
端点接收到连接请求。 - 后端完成认证和流数据验证后,将连接交给
ChatManager
进行处理。
- 前端连接到
- 服务层管理连接:
ChatManager.handle_websocket
接收到连接,初始化客户端处理器ChatClient
。- 开始消息接收循环,等待前端发送的消息。
- 接收并处理消息:
- 前端发送的消息通过
websocket.receive_json()
被后端接收并解析为payload
。 ChatClient.handle_message(payload)
被调用,负责具体的业务逻辑处理。
- 前端发送的消息通过
6.3 业务逻辑处理
在 ChatClient.handle_message(payload)
中,可能会执行以下操作(具体实现取决于 ChatClient
的定义,但根据项目常见模式,可以做如下假设):
- 解析消息:提取消息内容和相关参数。
- 业务逻辑处理:根据消息内容调用相应的服务或处理函数,如查询数据库、调用语言模型生成回复等。
- 生成响应:创建一个响应消息对象,准备发送回前端。
6.4 发送响应消息
- 服务层发送消息:
- 处理完消息后,服务层使用
ChatManager.send_json
或ChatManager.send_message
方法将响应发送回前端。
- 处理完消息后,服务层使用
async def send_json(self, client_id: str, chat_id: str, message: ChatMessage, add=True):message.flow_id = client_idmessage.chat_id = chat_idwebsocket = self.active_connections[get_cache_key(client_id, chat_id)]if add:self.chat_history.add_message(client_id, chat_id, message)await websocket.send_json(message.dict())
解释:
- 参数:
client_id
和chat_id
:用于查找对应的 WebSocket 连接。message
:要发送的消息对象,通常为ChatMessage
类型。add
:是否将消息记录到聊天历史中。
- 流程:
- 将
client_id
和chat_id
添加到消息中。 - 查找对应的 WebSocket 连接。
- 如果
add
为True
,将消息添加到聊天历史记录。 - 通过
websocket.send_json
方法将消息以 JSON 格式发送给前端。
- 将
- 前端接收响应:
socket.onmessage = function(event) {const message = JSON.parse(event.data);console.log("收到消息:", message);// 在页面上显示消息
};
解释:
- 前端的
onmessage
事件处理函数接收到后端发送的 JSON 消息后,解析并处理显示。
7. 断开连接和错误处理
在 WebSocket 通信过程中,连接可能会因为各种原因断开,如用户关闭浏览器、网络问题或服务器错误。后端需要妥善处理这些情况,确保资源被正确释放,避免内存泄漏或其他问题。
7.1 客户端断开连接
当客户端主动断开 WebSocket 连接时,后端 ChatManager.handle_websocket
会捕获 WebSocketDisconnect
异常:
except WebSocketDisconnect as e:logger.info('Client disconnected: {}'.format(e))
解释:
- 记录断开连接的日志,便于后续调试和监控。
7.2 后端主动关闭连接
后端在检测到某些错误或异常情况下,可以主动关闭 WebSocket 连接:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason=message)
解释:
code
:WebSocket 关闭代码,表明连接关闭的原因。reason
:具体的关闭原因,便于前端理解和处理。
7.3 异常处理
在消息处理过程中,可能会发生各种异常,后端需要捕获并处理:
except Exception as exc:logger.exception(str(exc))await self.close_connection(flow_id=flow_id,chat_id=chat_id,code=status.WS_1011_INTERNAL_ERROR,reason='后端未知错误类型',key_list=key_list)
解释:
- 记录异常:使用日志记录详细的错误信息,便于调试。
- 关闭连接:通过
close_connection
方法关闭 WebSocket 连接,确保资源被释放。
7.4 资源清理
在 finally
块中,确保所有连接和任务被正确清理:
finally:thread_pool.cancel_task(key_list) # 取消进行中的任务await self.close_connection(flow_id=flow_id,chat_id=chat_id,code=status.WS_1000_NORMAL_CLOSURE,reason='Client disconnected',key_list=key_list)self.disconnect(flow_id, chat_id)
解释:
- 取消任务:确保所有与连接相关的后台任务被取消,避免资源泄漏。
- 关闭连接:调用
close_connection
方法关闭 WebSocket 连接。 - 断开连接:从
active_connections
中移除连接,释放资源。
8. 总结与建议
通过上述解析,你应该对项目中 WebSocket 的整体通信流程有了清晰的理解。以下是一些关键点和建议,帮助你更好地掌握和应用 WebSocket 技术:
- 理解 WebSocket 基础:熟悉 WebSocket 的基本概念、工作原理以及与 HTTP 的区别。了解全双工通信和持久连接的优势。
- 熟悉 FastAPI 的 WebSocket 功能:
- 学习 FastAPI 如何定义和管理 WebSocket 端点。
- 理解
WebSocket
对象的方法,如accept()
,send_text()
,send_json()
,receive_text()
,receive_json()
,close()
等。
- 异步编程:
- WebSocket 通信通常涉及大量并发连接和异步操作,熟练掌握
async
和await
关键字,理解asyncio
的基本概念。
- WebSocket 通信通常涉及大量并发连接和异步操作,熟练掌握
- 连接管理:
- 使用合适的数据结构(如字典)管理活跃的 WebSocket 连接,确保能够快速查找和发送消息。
- 实现连接的建立、维护和断开机制,确保资源被正确管理。
- 消息处理:
- 定义清晰的消息协议,确保前后端之间的数据格式一致。
- 在服务层处理具体的业务逻辑,保持 API 层的简洁。
- 错误处理与资源清理:
- 捕获和处理各种可能的异常,确保 WebSocket 连接能够在异常情况下被正确关闭。
- 在连接断开或发生错误时,确保所有相关资源被释放,避免内存泄漏。
- 安全性:
- 在建立 WebSocket 连接时,进行身份验证和授权,确保只有合法用户可以连接和发送消息。
- 处理潜在的安全漏洞,如消息注入、拒绝服务攻击等。
- 调试与日志记录:
- 使用日志记录关键事件和错误信息,帮助调试和监控 WebSocket 连接的状态。
- 定期检查日志,识别和解决潜在问题。
示例流程图
为了更直观地理解 WebSocket 通信流程,下面是一个简化的流程图:
前端 后端 API 层 (`api/v1/chat.py`) 服务层 (`chat/manager.py`)| | || --建立 WebSocket 连接-----> | || | --认证和验证流数据--------------> || | | --添加到 active_connections| | <----------连接成功/失败----------- || --发送消息(JSON)----------> | || | --转发消息到 ChatManager || | | --处理消息逻辑| | | --生成响应| | <---------发送响应消息------------ || <--------接收响应消息-------- | || | || --断开连接------------------> | || | --处理断开连接和资源清理----------> || | | --移除连接| | <---------断开连接确认----------- |
示例流程图的详细优化:
-
用户提交申请 (Client -> Web Server)
-
数据格式: HTTP POST 请求,内容为 JSON 格式。
-
数据内容:
{"user_id": "12345","application_type": "loan","amount": 10000,"submission_date": "2024-12-04" }
-
-
Web Server 验证数据完整性与格式 (Web Server)
- 检查项:
user_id
是否存在且格式正确。application_type
是否有效(例如,“loan”)。amount
是否为数字且符合申请的业务规则。
- 响应:
- 如果验证失败,返回 400 错误,并说明错误原因。
- 如果验证成功,将数据传递给下一步服务。
- 检查项:
-
Web Server 转发数据到认证服务 (Web Server -> Authentication Service)
-
数据格式: HTTP POST 请求,内容为 JSON 格式。
-
数据内容:
{"user_id": "12345","application_type": "loan","amount": 10000 }
-
认证服务逻辑:
- 校验
user_id
是否有效,检查用户是否符合资格(如年龄、信用评分等)。 - 通过数据库或外部 API 检查用户状态。
- 校验
-
-
认证服务返回认证结果 (Authentication Service -> Web Server)
-
数据格式: HTTP 响应,JSON 格式。
-
数据内容:
{"user_id": "12345","status": "approved", // 或 "rejected""reason": "User meets credit criteria" }
-
处理:
- 如果认证失败(例如,信用评分不足),
status
为 “rejected”。 - 如果认证成功,
status
为 “approved”,并传递给业务逻辑处理层。
- 如果认证失败(例如,信用评分不足),
-
-
Web Server 转发数据到业务逻辑服务 (Web Server -> Business Logic Service)
-
数据格式: HTTP POST 请求,内容为 JSON 格式。
-
数据内容:
{"user_id": "12345","status": "approved","amount": 10000,"submission_date": "2024-12-04" }
-
业务逻辑:
- 进行更复杂的业务逻辑,例如对
amount
进行规则校验、计算利息、生成合同内容等。
- 进行更复杂的业务逻辑,例如对
-
-
业务逻辑服务返回处理结果 (Business Logic Service -> Web Server)
-
数据格式: HTTP 响应,JSON 格式。
-
数据内容:
{"application_id": "67890","user_id": "12345","loan_amount": 10000,"status": "approved","loan_terms": {"interest_rate": 5.5,"duration_months": 12},"approval_date": "2024-12-04" }
-
处理:
- 如果业务逻辑成功,生成申请 ID,返回申请详情。
- 如果失败(例如,金额不符或条件不满足),返回失败的具体原因。
-
-
Web Server 提交数据到第三方支付/账户服务 (Web Server -> Payment Service)
-
数据格式: HTTP POST 请求,内容为 JSON 格式。
-
数据内容:
{"user_id": "12345","loan_id": "67890","loan_amount": 10000,"payment_method": "bank_transfer","account_details": {"account_number": "987654321","bank_name": "ABC Bank"} }
-
支付服务:
- 根据贷款信息和用户的账户信息,处理资金发放流程。
-
-
支付服务返回支付结果 (Payment Service -> Web Server)
-
数据格式: HTTP 响应,JSON 格式。
-
数据内容:
{"loan_id": "67890","payment_status": "success", // 或 "failure""payment_date": "2024-12-05","transaction_id": "TX123456789" }
-
处理:
- 如果支付成功,返回成功信息及支付凭证。
- 如果支付失败,返回失败原因和错误代码。
-
-
Web Server 返回最终响应给用户 (Web Server -> Client)
-
数据格式: HTTP 响应,JSON 格式。
-
数据内容:
{"user_id": "12345","application_id": "67890","loan_status": "approved","payment_status": "success","loan_terms": {"interest_rate": 5.5,"duration_months": 12},"transaction_id": "TX123456789","message": "Your loan application has been successfully processed." }
-
相关文章:
websocket通信
“WebSocket 允许客户端和服务器在连接建立后随时互相发送数据,而无需每次交互都重新建立连接。”我想请问,第一次前端往后端发送数据时,传递的数据应该满足接口的参数内容,在第一次建立连接后之后的数据传递还是要满足接口的参数…...
数据结构——单调队列
这篇博客我们来讨论一下单调队列的问题,其实和之前学的单调栈都是一种上通过改变操作来解决问题的一种数据结构 我们先来回忆一下单调栈的内容,这样方便将其和单调队列做区分 单调栈:(单调性从栈底到栈顶) 1.单调栈是一种栈数据…...
qt环境 C11thread子线程关闭定时器问题
环境情况:使用的是thread c11线程和qt的定时器 报错: QObject::~QObject: Timers cannot be stopped from another thread 主要原因: 1.开启了一个事件循环线程处理消息类型,但是有一种消息类型需要关闭资源,这就导…...
深入浅出:虚拟化技术及其在现代 IT 中的应用
文章目录 虚拟化的定义与基本原理虚拟机监控程序(Hypervisor) 虚拟化的历史与发展虚拟化的实现方式虚拟化的优势1. 提高资源利用率2. 降低成本3. 提升灵活性和可扩展性4. 加快应用部署和迁移5. 提高安全性和隔离性 不同类型虚拟化技术服务器虚拟化实际应…...
Golang内存模型总结1(mspan、mcache、mcentral、mheap)
1.内存模型 1.1 操作系统存储模型 从上到下分别是寄存器、高速缓存、内存、磁盘,其中越往上速度越快,空间越小,价格越高。 关键词是多级模型和动态切换 1.2 虚拟内存与物理内存 虚拟内存是一种内存管理技术,允许计算机使用比…...
优先算法 —— 滑动窗口系列 - 无重复字符的最长子串
目录 前言 1. 无重复字符的最长子串 2. 题目解析 3. 算法原理 解法1:暴力枚举 哈希表(判断字符是否有重复出现) 解法2:滑动窗口 4. 代码 前言 当我们发现暴力解法两个指针都不回退,都是向同一个方向移动的时候我…...
Python 浏览器自动化新利器:DrissionPage,让网页操作更简单!
Python 浏览器自动化新利器:DrissionPage,让网页操作更简单! 文章目录 Python 浏览器自动化新利器:DrissionPage,让网页操作更简单!🚀 引言🌟 DrissionPage简介🛠️ 三大…...
[Python] 进阶之路:模块、包和异常处理
在掌握了Python的类与对象后,下一步是深入理解模块化开发和异常处理。模块与包帮助我们组织代码,增强代码的可维护性和重用性,而异常处理则是编写健壮代码的重要技能。本文将系统讲解Python中模块、包和异常处理的核心概念与实用技巧。 一、模…...
SpringBoot 整合 Avro 与 Kafka 详解
SpringBoot 整合 Avro 与 Kafka 详解 在大数据处理和实时数据流场景中,Apache Kafka 和 Apache Avro 是两个非常重要的工具。Kafka 作为一个分布式流处理平台,能够高效地处理大量数据,而 Avro 则是一个用于序列化数据的紧凑、快速的二进制数…...
windows C#-使用 Override 和 New 关键字(上)
在 C# 中,派生类中的方法可具有与基类中的方法相同的名称。 可使用 new 和 override 关键字指定方法的交互方式。 override 修饰符用于扩展基类 virtual 方法,而 new 修饰符用于隐藏可访问的基类方法 。 在控制台应用程序中,声明以下两个类…...
FaRM译文
No compromises: distributed transactions with consistency, availability, and performance Aleksandar Dragojevic, Dushyanth Narayanan, Edmund B. Nightingale, Matthew Renzelmann, Alex Shamis, Anirudh Badam, Miguel Castro Microsoft Research 摘要 具有强一致…...
大数据新视界 -- Hive 元数据管理:核心元数据的深度解析(上)(27 / 30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
大数据项目-Django基于聚类算法实现的房屋售房数据分析及可视化系统
《[含文档PPT源码等]精品Django基于聚类算法实现的房屋售房数据分析及可视化系统》该项目含有源码、文档、PPT、配套开发软件、软件安装教程课程答疑等! 数据库管理工具:phpstudy/Navicat或者phpstudy/sqlyog 后台管理系统涉及技术: 后台使…...
当大的div中有六个小的div,上面三个下面三个,当外层div高变大的时候我希望里面的小的div的高也变大
问: 当大的div中有六个小的div,上面三个下面三个,当外层div高变大的时候我希望里面的小的div的高也变大 回答: 这时候我们就不能写死六个小的div的高度,否则上下的小的div的间距就会变大,因为他们的高度…...
使用 Postman 上传二进制类型的图片到后端接口写法
我们有的时候会有需求,就是通过 postman 传递二进制图片到后端接口,如下图: 那我们的 Java 接口需要怎么写呢? Spring Boot 接收这些数据的方式需要使用 RequestBody 注解来处理原始的二进制数据(byte[])。…...
字符串函数和内存函数
字符串函数 1、strlcpy 【字符串拷贝】 (将原字符串中的字符拷贝到目标字符数组中,包括终止符号\0,并在这里停止;为了避免越界,目标字符串数组应该足够大去接收)👆 (返回值是 dest…...
uC/OSII学习笔记(一)任务的增删改查
使用天玛智控的控制器,基础工程文件已移植ucosii。 正常的任务创建流程为: 1.OSInit(); 2.OSTaskCreate(); 3.OSStart(); 但是天玛对其有做修改,任务创建直接调用OSTaskCreate()函数即可,不用在…...
如何搭建JMeter分布式集群环境来进行性能测试
在性能测试中,当面对海量用户请求的压力测试时,单机模式的JMeter往往力不从心。如何通过分布式集群环境,充分发挥JMeter的性能测试能力?这正是许多测试工程师在面临高并发、海量数据时最关注的问题。那么,如何轻松搭建…...
蓝桥杯准备训练(lesson2 ,c++)
3.1 字符型 char //character的缩写在键盘上可以敲出各种字符,如: a , q , , # 等,这些符号都被称为字符,字符是⽤单引号括 起来的,如: ‘a’ , ‘b’ &…...
【踩坑】Collectors.toMap 抛出 NullPointerException 异常
1. 场景重现 public class Test01 {public static void main(String[] args) {List<Person> list Arrays.asList(new Person("anna", 17, 0), new Person("bob", 18, 1), new Person("jack", 20, null));Map<String, Integer> nam…...
泷羽sec专题课笔记-- Linux作业--开机自启动方法以及破解
本笔记为 泷羽sec 《红队全栈课程》学习笔记,课程请可自行前往B站学习,课程/笔记主要涉及网络安全相关知识、系统以及工具的介绍等,请使用该课程、本笔记以及课程和笔记中提及工具的读者,遵守网络安全相关法律法规,切勿…...
OpenCV
MFC(C)的使用 1、官网下载 https://opencv.org/ 选 Library - Release - 选择你需要的版本 2、安装 3、配置环境变量 将 OpenCV 的bin目录 C:\Program Files\OpenCV481\opencv\build\bin添加到系统的PATH环境变量中。这使得在运行程序时能够找到 Open…...
Wwise 使用MIDI文件、采样音频
第一种:当采样音频只有一个文件的时候 1.拖入MIDI文件到Interactive Music Hierarchy层级 2.拖入采样音频到Actor-Mixer Hierarchy层级 3.勾选MIDI显示出面板,设置Root Note与采样音频音高相同,这里是C#5 4.播放测试,成功&…...
OpenStack-Glance组件
Glance Glance使用磁盘格式和容器格式基础配置镜像转换 Glance 是 OpenStack 的镜像服务,负责存储、发现和管理虚拟机镜像。它允许用户创建和共享镜像,用于启动虚拟机实例。 Glance 的主要功能 (1)虚拟机镜像的管理 支持镜像的上…...
写译热点单词 | 50篇文章整理 | 手敲自用
目录 文化类 政治类 经济类 教育类 科技类 健康类 安全类 体育类 第二版 删去了部分不太常用的 文化类 1. 阴历: lunar calendar 2. 阳历: solar calendar 3. 春节: the Spring Festival 4. 除夕: Chinese New Year’s Eve 5. 清明节: Tomb Sweeping Day 6. 重阳…...
【UE5 C++】判断两点连线是否穿过球体
目录 前言 方法一 原理 代码 测试 结果 方法二 原理 一、检查连线与球体的相交情况 二、检查距离与球体半径的关系 三、检查连线与球体的相交 代码 前言 通过数学原理判断空间中任意两点的连线是否穿过球体,再通过射线检测检验算法的正确性。 方法一 …...
A1228 php+Mysql旅游供需平台的设计与实现 导游接单 旅游订单 旅游分享网站 thinkphp框架 源码 配置 文档 全套资料
旅游供需平台 1.项目描述2. 开发背景与意义3.项目功能4.界面展示5.源码获取 1.项目描述 随着社会经济的快速发展,生活水平的提高,人们对旅游的需求日益增强,因此,为给用户提供一个便利的查看导游信息,进行导游招募的平…...
【linux】服务器Ubuntu20.04安装cuda11.8教程
【linux】服务器Ubuntu20.04安装cuda11.8教程 文章目录 【linux】服务器Ubuntu20.04安装cuda11.8教程到官网找到对应版本下载链接终端操作cudnn安装到官网下载下载后解压进入解压后的目录:将头文件复制到 /usr/local/cuda/include/ 目录:将库文件复制到 …...
SpringMVC其他扩展
一、全局异常处理机制: 1.异常处理两种方式: 开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中,…...
用“*”构成一个倒三角形:JAVA
输入:5 输出: ******* ***** *** * 代码: import java.util.Scanner; //倒三角 public class FF6 {public static void main(String[] args) {Scanner scannernew Scanner(System.in);while (scanner.hasNextInt()){int nscanner…...
洛谷P2670扫雷游戏(Java)
三.P2670 [NOIP2015 普及组] 扫雷游戏 题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n 行 m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩…...
Windows 11 环境下 条码阅读器输入到记事本的内容不完整
使用Windows11时,为什么记事本应用程序中的扫描数据被截断或不完整?为什么sdo 特殊字符的显示与Windows 10 记事本应用程序不同? 很多人认为和中文输入法有关,其实主要问题出在这个windows11下的记事本程序上,大家知道这个就可以了&#x…...
C# 动态类型 Dynamic
文章目录 前言1. 什么是 Dynamic?2. 声明 Dynamic 变量3. Dynamic 的运行时类型检查4. 动态类型与反射的对比5. 使用 Dynamic 进行动态方法调用6. Dynamic 与 原生类型的兼容性7. 动态与 LINQ 的结合8. 结合 DLR 特性9. 动态类型的性能考虑10. 何时使用 Dynamic&…...
设计模式10:观察者模式(订阅-发布)
系列总链接:《大话设计模式》学习记录_net 大话设计-CSDN博客 参考:简说设计模式——工厂方法模式 - JAdam - 博客园 参考:简单工厂模式(Simple Factory Pattern) - 回忆酿的甜 - 博客园 一:概述 观察者模式࿰…...
2020 年 12 月青少年软编等考 C 语言四级真题解析
目录 T1. 开餐馆思路分析T2. 邮票收集思路分析T3. 带通配符的字符串匹配思路分析T4. 删除数字思路分析T1. 开餐馆 北大信息学院的同学小明毕业之后打算创业开餐馆。现在共有 n n n 个地点可供选择。小明打算从中选择合适的位置开设一些餐馆。这 n n n 个地点排列在同一条直线…...
高级java每日一道面试题-2024年12月03日-JVM篇-什么是Stop The World? 什么是OopMap? 什么是安全点?
如果有遗漏,评论区告诉我进行补充 面试官: 什么是Stop The World? 什么是OopMap? 什么是安全点? 我回答: 在Java虚拟机(JVM)中,Stop The World、OopMap 和 安全点 是与垃圾回收(GC)和性能优化密切相关的概念。理…...
探索 Apache Commons Collections 4:Java 集合框架的强大扩展
在 Java 开发中,集合框架是处理数据的核心工具。然而,标准 Java 集合框架虽然功能强大,但在某些场景下仍显得不够灵活。Apache Commons Collections 4(以下简称 commons-collections4)作为一个强大的工具库,…...
NIO(New IO)和BIO(Blocking IO)的区别
Java中的NIO(New IO)和BIO(Blocking IO)的区别及NIO的核心组件 Java中的NIO(New IO)和BIO(Blocking IO)是两种不同的网络通信模型,各自具有独特的特性和适用场景。下面将…...
【串口助手开发】visual studio 使用C#开发串口助手,生成在其他电脑上可执行文件,可运行的程序
1、改成Release,生成解决方案 串口助手调试成功后,将Debug改为Release,点击生成解决方案 2、运行exe文件 生成解决方案后,在bin文件夹下, Release文件夹下,生成相关文件 复制一整个Release文件夹…...
Linux 编译 convert_geotiff 时遇到的几个问题
步骤1:安装libgeotiff-dev 在ubuntu上,安装命令为: sudo apt-get install libgeotiff-dev在macos上,安装命令为: brew install libgeotiff在Linux上安装命令为: sudo yum install libgeotiff-devel注意…...
执行存储过程报:This function has none of DETERMINISTIC, NO SQL ???
执行存储过程时报如下错你该怎么整? [Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)来…...
JAVA |日常开发中Servlet详解
JAVA |日常开发中Servlet详解 前言一、Servlet 概述1.1 定义1.2 历史背景 二、Servlet 的生命周期2.1 加载和实例化2.2 初始化(init 方法)2.3 服务(service 方法)2.4 销毁(destroy 方法) 三、Se…...
Spring Cloud Alibaba 之 “Sentinel”
从网上下载好sentinel-dashboard-1.6.3.jar,然后执行 java -jar sentinel-dashboard-1.6.3.jar,执行成功之后在浏览器输入localhost:8080,Sentinel的登录名和密码都是sentinel,登陆成功之后看到只有一个首页。 接下来开始整合Spring Cloud Alibaba Sen…...
UE4外挂实现分析-PC端-附源码
UE4外挂实现分析-PC端 游戏分析 分析工具: Cheat Engine 7.5 x64dbg IDA Pro 参考文章: UE4逆向笔记之GWORLD GName GameInstance - 小透明‘s Blog 【项目源码下载】https://download.csdn.net/download/Runnymmede/90079718 本次分析的游戏使用UE4.2…...
力扣88题:合并两个有序数组
力扣88题:合并两个有序数组 题目描述 给定两个按非递减顺序排列的整数数组 nums1 和 nums2,以及它们的长度 m 和 n,要求将 nums2 合并到 nums1,使得合并后的数组仍按非递减顺序排列。 输入与输出 示例 1: 输入&am…...
Lua面向对象实现
Lua中的面向对象是通过表(table)来模拟类实现的,通过setmetatable(table,metatable)方法,将一个表设置为当前表的元表,之后在调用当前表没有的方法或者键时,会再查询元表中的方法和键,以此来实现…...
小程序 模版与配置
WXML模版语法 一、数据绑定 1、数据绑定的基本原则 (1)在data中定义数据 (2)在WXML中使用数据 2、在data中定义页面的数据 3、Mustache语法的格式(双大括号) 4、Mustache语法的应用场景 (…...
【Elasticsearch】实现分布式系统日志高效追踪
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
探索Go语言中的循环单链表
简介 循环单链表是一种特殊的链表数据结构,它的最后一个节点指向链表的头节点,形成一个闭环。今天我们将探讨如何在Go语言中实现和操作这种数据结构。 为什么选择循环单链表? 连续访问:在循环单链表中,可以无限循环…...
[go-redis]客户端的创建与配置说明
创建redis client 使用go-redis库进行创建redis客户端比较简单,只需要调用redis.NewClient接口创建一个客户端 redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379",Password: "",DB: 0, })NewClient接口只接收一个参数red…...