1use std::sync::Arc;
2use crate::{YrsManager, SyncService};
3use crate::sync_service::{RoomInfo, RoomStatus};
4use warp::ws::{WebSocket, Ws};
5use warp::{Filter, Rejection, Reply};
6use yrs_warp::broadcast::BroadcastGroup;
7use yrs_warp::ws::{WarpSink, WarpStream};
8use tokio::sync::Mutex;
9use futures_util::StreamExt;
10use mf_model::tree::Tree;
11use serde_json::json;
12
13#[derive(Debug)]
15pub struct RoomNotFoundError {
16 room_id: String,
17}
18
19impl warp::reject::Reject for RoomNotFoundError {}
20
21impl RoomNotFoundError {
22 pub fn new(room_id: String) -> Self {
23 Self { room_id }
24 }
25
26 pub fn room_id(&self) -> &str {
27 &self.room_id
28 }
29}
30
31#[derive(Clone)]
33pub struct CollaborationServer {
34 yrs_manager: Arc<YrsManager>,
35 sync_service: Arc<SyncService>,
36 port: u16,
37}
38
39impl CollaborationServer {
40 pub fn new(
41 yrs_manager: Arc<YrsManager>,
42 port: u16,
43 ) -> Self {
44 let sync_service = Arc::new(SyncService::new(yrs_manager.clone()));
45 Self { yrs_manager, sync_service, port }
46 }
47
48 pub fn with_sync_service(
50 yrs_manager: Arc<YrsManager>,
51 sync_service: Arc<SyncService>,
52 port: u16,
53 ) -> Self {
54 Self { yrs_manager, sync_service, port }
55 }
56
57 pub async fn handle_rejection(
59 err: Rejection
60 ) -> Result<impl Reply, std::convert::Infallible> {
61 if let Some(room_error) = err.find::<RoomNotFoundError>() {
62 let error_response = json!({
63 "error": "ROOM_NOT_FOUND",
64 "message": format!("房间 '{}' 不存在", room_error.room_id()),
65 "room_id": room_error.room_id(),
66 "code": 404
67 });
68
69 let reply = warp::reply::with_status(
70 warp::reply::json(&error_response),
71 warp::http::StatusCode::NOT_FOUND,
72 );
73
74 return Ok(reply.into_response());
75 }
76
77 if err.is_not_found() {
79 let error_response = json!({
80 "error": "NOT_FOUND",
81 "message": "请求的资源不存在",
82 "code": 404
83 });
84
85 let reply = warp::reply::with_status(
86 warp::reply::json(&error_response),
87 warp::http::StatusCode::NOT_FOUND,
88 );
89
90 return Ok(reply.into_response());
91 }
92
93 let error_response = json!({
95 "error": "INTERNAL_SERVER_ERROR",
96 "message": "服务器内部错误",
97 "code": 500
98 });
99
100 let reply = warp::reply::with_status(
101 warp::reply::json(&error_response),
102 warp::http::StatusCode::INTERNAL_SERVER_ERROR,
103 );
104
105 Ok(reply.into_response())
106 }
107
108 pub async fn init_room_with_data(
111 &self,
112 room_id: &str,
113 tree: &Tree,
114 ) -> crate::Result<()> {
115 tracing::info!("🔄 初始化房间: '{}' 使用数据", room_id);
116
117 self.sync_service.init_room_with_tree(room_id, tree).await?;
118 tracing::info!("🔄 房间 '{}' 初始化完成", room_id);
119
120 Ok(())
121 }
122
123 pub async fn offline_room(
129 &self,
130 room_id: &str,
131 save_data: bool,
132 ) -> crate::Result<bool> {
133 tracing::info!("🔄 开始下线房间: {}", room_id);
134
135 let room_status = self.sync_service.get_room_status(room_id).await;
137 match room_status {
138 RoomStatus::NotExists => {
139 tracing::warn!("❌ 尝试下线不存在的房间: {}", room_id);
140 return Ok(false);
141 },
142 RoomStatus::Offline => {
143 tracing::info!("ℹ️ 房间 {} 已经下线", room_id);
144 return Ok(true);
145 },
146 _ => {},
147 }
148
149 if let Some(room_info) = self.sync_service.get_room_info(room_id).await
151 {
152 tracing::info!(
153 "📊 房间信息 - 节点数: {}, 客户端数: {}",
154 room_info.node_count,
155 room_info.client_count
156 );
157 }
158
159 match self.sync_service.offline_room(room_id, save_data).await {
161 Ok(snapshot) => {
162 if let Some(_snapshot) = snapshot {
163 tracing::info!("💾 房间 {} 数据已保存", room_id);
164 }
165 tracing::info!("✅ 房间 {} 成功下线", room_id);
166 Ok(true)
167 },
168 Err(e) => {
169 tracing::error!("❌ 房间 {} 下线失败: {}", room_id, e);
170 Err(e)
171 },
172 }
173 }
174
175 pub async fn force_offline_room(
177 &self,
178 room_id: &str,
179 ) -> crate::Result<bool> {
180 tracing::warn!("⚠️ 强制下线房间: {}", room_id);
181
182 match self.sync_service.force_offline_room(room_id).await {
183 Ok(success) => {
184 if success {
185 tracing::info!("✅ 房间 {} 强制下线成功", room_id);
186 } else {
187 tracing::error!("❌ 房间 {} 强制下线失败", room_id);
188 }
189 Ok(success)
190 },
191 Err(e) => {
192 tracing::error!(
193 "❌ 强制下线房间 {} 时发生错误: {}",
194 room_id,
195 e
196 );
197 Err(e)
198 },
199 }
200 }
201
202 pub async fn offline_rooms(
204 &self,
205 room_ids: &[String],
206 save_data: bool,
207 ) -> crate::Result<Vec<(String, bool)>> {
208 tracing::info!("🔄 批量下线 {} 个房间", room_ids.len());
209
210 let mut results = Vec::new();
211
212 for room_id in room_ids {
213 match self.offline_room(room_id, save_data).await {
214 Ok(success) => results.push((room_id.clone(), success)),
215 Err(e) => {
216 tracing::error!("❌ 下线房间 {} 失败: {}", room_id, e);
217 results.push((room_id.clone(), false));
218 },
219 }
220 }
221
222 let successful_count =
223 results.iter().filter(|(_, success)| *success).count();
224 tracing::info!(
225 "📊 批量下线完成: {}/{} 个房间成功下线",
226 successful_count,
227 room_ids.len()
228 );
229
230 Ok(results)
231 }
232
233 pub fn get_active_rooms(&self) -> Vec<String> {
235 self.sync_service.get_active_rooms()
236 }
237
238 pub async fn get_rooms_stats(&self) -> Vec<RoomInfo> {
240 self.sync_service.get_rooms_stats().await
241 }
242
243 pub async fn offline_rooms_by_condition<F>(
246 &self,
247 condition: F,
248 save_data: bool,
249 ) -> crate::Result<Vec<String>>
250 where
251 F: Fn(&RoomInfo) -> bool,
252 {
253 let room_stats = self.get_rooms_stats().await;
254 let rooms_to_offline: Vec<String> = room_stats
255 .into_iter()
256 .filter(|info| condition(info))
257 .map(|info| info.room_id)
258 .collect();
259
260 if rooms_to_offline.is_empty() {
261 tracing::info!("🔍 没有房间满足下线条件");
262 return Ok(vec![]);
263 }
264
265 tracing::info!("🎯 找到 {} 个房间满足下线条件", rooms_to_offline.len());
266
267 let results = self.offline_rooms(&rooms_to_offline, save_data).await?;
268 let successful_rooms: Vec<String> = results
269 .into_iter()
270 .filter_map(
271 |(room_id, success)| if success { Some(room_id) } else { None },
272 )
273 .collect();
274
275 Ok(successful_rooms)
276 }
277
278 pub async fn offline_empty_rooms(
280 &self,
281 save_data: bool,
282 ) -> crate::Result<Vec<String>> {
283 tracing::info!("🔍 搜索并下线空房间");
284
285 self.offline_rooms_by_condition(
286 |room_info| room_info.client_count == 0,
287 save_data,
288 )
289 .await
290 }
291
292 pub async fn offline_inactive_rooms(
294 &self,
295 inactive_duration: std::time::Duration,
296 save_data: bool,
297 ) -> crate::Result<Vec<String>> {
298 let now = std::time::SystemTime::now();
299 tracing::info!(
300 "🔍 搜索并下线超过 {:?} 未活动的房间",
301 inactive_duration
302 );
303
304 self.offline_rooms_by_condition(
305 |room_info| {
306 now.duration_since(room_info.last_activity).unwrap_or_default()
307 > inactive_duration
308 },
309 save_data,
310 )
311 .await
312 }
313
314 pub async fn shutdown(
316 &self,
317 save_all_data: bool,
318 ) -> crate::Result<()> {
319 tracing::info!("🔴 开始服务器关闭流程");
320
321 let all_rooms = self.get_active_rooms();
322 if all_rooms.is_empty() {
323 tracing::info!("ℹ️ 没有活跃房间需要关闭");
324 return Ok(());
325 }
326
327 tracing::info!("📊 准备关闭 {} 个房间", all_rooms.len());
328
329 let results = self.offline_rooms(&all_rooms, save_all_data).await?;
331 let successful_count =
332 results.iter().filter(|(_, success)| *success).count();
333
334 tracing::info!(
335 "✅ 服务器关闭完成: {}/{} 个房间成功下线",
336 successful_count,
337 all_rooms.len()
338 );
339
340 if successful_count != all_rooms.len() {
341 tracing::warn!("⚠️ 部分房间下线失败,可能需要手动清理");
342 }
343
344 Ok(())
345 }
346
347 pub async fn start(self) {
349 let server = self.clone(); let ws_route = warp::path("collaboration")
353 .and(warp::path::param::<String>()) .and(warp::ws())
355 .and(warp::any().map(move || server.clone()))
356 .and_then(Self::ws_handler);
357
358 let server_for_http = self.clone();
360 let room_check_route = warp::path("collaboration")
361 .and(warp::path("room-check"))
362 .and(warp::path::param::<String>()) .and(warp::get())
364 .and(warp::any().map(move || server_for_http.clone()))
365 .and_then(Self::room_check_handler);
366
367 let server_for_health = self.clone();
369 let health_route = warp::path("health")
370 .and(warp::get())
371 .and(warp::any().map(move || server_for_health.clone()))
372 .and_then(Self::health_check_handler);
373
374 let server_for_status = self.clone();
376 let room_status_route = warp::path("collaboration")
377 .and(warp::path("rooms"))
378 .and(warp::path::param::<String>()) .and(warp::path("status"))
380 .and(warp::get())
381 .and(warp::any().map(move || server_for_status.clone()))
382 .and_then(Self::room_status_handler);
383
384 let routes = ws_route
386 .or(room_check_route)
387 .or(health_route)
388 .or(room_status_route)
389 .recover(Self::handle_rejection) .with(
391 warp::cors()
392 .allow_any_origin()
393 .allow_headers(vec!["content-type"])
394 .allow_methods(vec![
395 "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS",
396 ]),
397 );
398
399 let addr = ([0, 0, 0, 0], self.port);
400 tracing::info!(
401 "🌐 协作服务器启动于 http://{}:{}",
402 addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
403 addr.1
404 );
405 tracing::info!(
406 "📡 WebSocket: ws://{}:{}/collaboration/{{room_id}}",
407 addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
408 addr.1
409 );
410 tracing::info!(
411 "🔍 房间检查: http://{}:{}/collaboration/room-check/{{room_id}}",
412 addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
413 addr.1
414 );
415 tracing::info!(
416 "💚 健康检查: http://{}:{}/health",
417 addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
418 addr.1
419 );
420 tracing::info!(
421 "📊 房间状态: http://{}:{}/collaboration/rooms/{{room_id}}/status",
422 addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
423 addr.1
424 );
425
426 warp::serve(routes).run(addr).await;
427 }
428
429 async fn ws_handler(
431 room_id: String,
432 ws: Ws,
433 server: CollaborationServer,
434 ) -> Result<impl Reply, Rejection> {
435 let yrs_manager = server.yrs_manager.clone();
436
437 if !yrs_manager.room_exists(&room_id) {
439 tracing::warn!("❌ 客户端尝试连接不存在的房间: {}", room_id);
440
441 return Err(warp::reject::custom(RoomNotFoundError::new(room_id)));
443 }
444
445 let awareness_ref = match yrs_manager.get_awareness_ref(&room_id) {
447 Some(awareness) => awareness,
448 None => {
449 tracing::error!("🚨 房间 {} 存在但无法获取 awareness", room_id);
451 return Err(warp::reject::custom(RoomNotFoundError::new(
452 room_id,
453 )));
454 },
455 };
456
457 Ok(ws.on_upgrade(move |socket| async move {
458 tracing::info!("✅ 客户端成功连接到现有房间: {}", room_id);
459
460 let bcast = Arc::new(BroadcastGroup::new(awareness_ref, 128).await);
462 Self::peer(socket, bcast, room_id.clone()).await;
463 }))
464 }
465
466 async fn peer(
468 ws: WebSocket,
469 bcast: Arc<BroadcastGroup>,
470 room_id: String,
471 ) {
472 let (sink, stream) = ws.split();
473 let sink = Arc::new(Mutex::new(WarpSink::from(sink)));
474 let stream = WarpStream::from(stream);
475
476 let client_addr = "unknown"; tracing::info!(
479 "🔗 新客户端连接到房间: {} (地址: {})",
480 room_id,
481 client_addr
482 );
483
484 let sub = bcast.subscribe(sink, stream);
485
486 match sub.completed().await {
487 Ok(_) => {
488 tracing::info!(
489 "✅ 客户端正常断开连接 - 房间: {} (地址: {})",
490 room_id,
491 client_addr
492 );
493 },
494 Err(e) => {
495 let error_msg = format!("{}", e);
497
498 if error_msg.contains("failed to deserialize message") {
499 tracing::warn!(
500 "⚠️ 客户端发送了无效数据包 - 房间: {}, 错误: {}",
501 room_id,
502 error_msg
503 );
504 tracing::debug!(
505 "💡 这通常是由网络中断或客户端异常关闭导致的,属于正常现象"
506 );
507 } else if error_msg.contains("unexpected end of buffer") {
508 tracing::warn!(
509 "⚠️ 数据包不完整 - 房间: {}, 可能是网络传输中断",
510 room_id
511 );
512 } else if error_msg.contains("connection closed")
513 || error_msg.contains("broken pipe")
514 {
515 tracing::info!(
516 "🔌 客户端连接意外断开 - 房间: {} ({})",
517 room_id,
518 error_msg
519 );
520 } else {
521 tracing::error!(
522 "❌ 客户端连接异常 - 房间: {}, 错误: {}",
523 room_id,
524 error_msg
525 );
526 }
527 },
528 }
529 }
530
531 pub fn sync_service(&self) -> &Arc<SyncService> {
533 &self.sync_service
534 }
535
536 async fn room_check_handler(
538 room_id: String,
539 server: CollaborationServer,
540 ) -> Result<impl Reply, Rejection> {
541 tracing::debug!("🔍 检查房间是否存在: {}", room_id);
542
543 let exists = server.yrs_manager.room_exists(&room_id);
544
545 if exists {
546 let room_info = server.sync_service.get_room_info(&room_id).await;
547
548 let response = json!({
549 "exists": true,
550 "room_id": room_id,
551 "status": "available",
552 "info": room_info
553 });
554
555 tracing::debug!("✅ 房间 {} 存在", room_id);
556 Ok(warp::reply::with_status(
557 warp::reply::json(&response),
558 warp::http::StatusCode::OK,
559 ))
560 } else {
561 let response = json!({
562 "exists": false,
563 "room_id": room_id,
564 "status": "not_found",
565 "message": format!("房间 '{}' 不存在", room_id)
566 });
567
568 tracing::debug!("❌ 房间 {} 不存在", room_id);
569 Ok(warp::reply::with_status(
570 warp::reply::json(&response),
571 warp::http::StatusCode::NOT_FOUND,
572 ))
573 }
574 }
575
576 async fn health_check_handler(
578 server: CollaborationServer
579 ) -> Result<impl Reply, Rejection> {
580 let room_stats = server.sync_service.get_rooms_stats().await;
581 let active_rooms = server.sync_service.get_active_rooms();
582
583 let response = json!({
584 "status": "healthy",
585 "timestamp": std::time::SystemTime::now()
586 .duration_since(std::time::UNIX_EPOCH)
587 .unwrap_or_default()
588 .as_secs(),
589 "service": "ModuForge Collaboration Server",
590 "version": env!("CARGO_PKG_VERSION"),
591 "statistics": {
592 "active_rooms": active_rooms.len(),
593 "total_rooms": room_stats.len(),
594 "rooms": active_rooms
595 }
596 });
597
598 Ok(warp::reply::with_status(
599 warp::reply::json(&response),
600 warp::http::StatusCode::OK,
601 ))
602 }
603
604 async fn room_status_handler(
606 room_id: String,
607 server: CollaborationServer,
608 ) -> Result<impl Reply, Rejection> {
609 tracing::debug!("📊 获取房间状态: {}", room_id);
610
611 if let Some(room_info) =
612 server.sync_service.get_room_info(&room_id).await
613 {
614 let response = json!({
615 "room_id": room_id,
616 "status": room_info.status,
617 "node_count": room_info.node_count,
618 "client_count": room_info.client_count,
619 "last_activity": room_info.last_activity
620 .duration_since(std::time::UNIX_EPOCH)
621 .unwrap_or_default()
622 .as_secs(),
623 "available": true
624 });
625
626 Ok(warp::reply::with_status(
627 warp::reply::json(&response),
628 warp::http::StatusCode::OK,
629 ))
630 } else {
631 let response = json!({
632 "room_id": room_id,
633 "status": "not_found",
634 "available": false,
635 "message": format!("房间 '{}' 不存在", room_id)
636 });
637
638 Ok(warp::reply::with_status(
639 warp::reply::json(&response),
640 warp::http::StatusCode::NOT_FOUND,
641 ))
642 }
643 }
644}