From 69945b730fd3f6b6138ce50e49fc3392fcd74d71 Mon Sep 17 00:00:00 2001
From: yj <2077506045@qq.com>
Date: 星期一, 28 七月 2025 18:16:52 +0800
Subject: [PATCH] 新增关键词过滤;新增活跃客服统计;新增掉线通知

---
 app/workers/online_status_worker.py |  188 ++++
 tests/test_message_processor.py     |  130 ++
 app/services/silence_service.py     |  253 ++++++
 tests/test_silence_service.py       |  177 ++++
 config.example.json                 |   65 +
 example_usage.py                    |  136 +++
 app/services/email_service.py       |  149 +++
 logs/app.log                        |   15 
 app/services/group_stats_service.py |  251 +++++
 tests/test_online_status_monitor.py |  168 ++++
 app/services/dify_client.py         |   26 
 test_group_stats.py                 |  119 ++
 app/services/ecloud_client.py       |   82 +
 app/services/sms_service.py         |  143 +++
 E云管家接口文档.txt                        |   48 +
 app/api/silence_mode.py             |  191 ++++
 config.py                           |  109 ++
 app/api/group_stats.py              |  154 +++
 短信发送接口文档.txt                        |   61 +
 app/services/message_processor.py   |   47 
 20 files changed, 2,483 insertions(+), 29 deletions(-)

diff --git "a/E\344\272\221\347\256\241\345\256\266\346\216\245\345\217\243\346\226\207\346\241\243.txt" "b/E\344\272\221\347\256\241\345\256\266\346\216\245\345\217\243\346\226\207\346\241\243.txt"
index a275d7f..c7f58c0 100644
--- "a/E\344\272\221\347\256\241\345\256\266\346\216\245\345\217\243\346\226\207\346\241\243.txt"
+++ "b/E\344\272\221\347\256\241\345\256\266\346\216\245\345\217\243\346\226\207\346\241\243.txt"
@@ -339,4 +339,52 @@
     "message": "澶辫触",
     "code": "1001",
     "data": null
+}
+
+
+# 鏌ヨ璐﹀彿涓湪绾跨殑寰俊鍒楄〃
+
+绠�瑕佹弿杩帮細
+姝ゆ帴鍙e簲鐢ㄥ満鏅槸鏌ヨ鍦ㄧ嚎鐨剋id鍜寃cid鍒楄〃
+
+璇锋眰URL锛�
+http://鍩熷悕鍦板潃/queryLoginWx
+
+璇锋眰鏂瑰紡锛�
+POST
+
+璇锋眰澶碒eaders锛�
+Content-Type锛歛pplication/json
+Authorization锛歔Authorization]
+
+鏃犲弬鏁帮細
+
+杩斿洖鏁版嵁锛�
+鍙傛暟鍚�	绫诲瀷	璇存槑
+code	string	1000鎴愬姛锛堝湪绾匡級锛�1001澶辫触锛堢绾匡級
+message	string	鍙嶉淇℃伅
+wcId	string	寰俊id
+wId	string	鐧诲綍瀹炰緥鏍囪瘑
+璇锋眰鍙傛暟绀轰緥
+
+{    
+
+}
+鎴愬姛杩斿洖绀轰緥
+
+{
+    "code": "1000",
+    "message": "鎴愬姛",
+    "data": [
+        {
+            "wcId": "wxid_i6qsbbjenju2",
+            "wId": "72223018-7f2a-4f4f-bfa3-26e47dbd61"
+        }
+    ]
+}
+澶辫触杩斿洖绀轰緥
+
+{
+    "code": "1001",
+    "message": "澶辫触"
 }
\ No newline at end of file
diff --git a/app/api/group_stats.py b/app/api/group_stats.py
new file mode 100644
index 0000000..154e255
--- /dev/null
+++ b/app/api/group_stats.py
@@ -0,0 +1,154 @@
+"""
+缇ょ粍缁熻API鎺ュ彛
+"""
+
+from typing import Optional
+from fastapi import APIRouter, HTTPException
+from pydantic import BaseModel
+from loguru import logger
+
+from app.services.group_stats_service import group_stats_service
+
+
+router = APIRouter()
+
+
+class GroupStatsResponse(BaseModel):
+    """缇ょ粍缁熻鍝嶅簲妯″瀷"""
+    success: bool
+    message: str
+    data: dict
+
+
+class ClearStatsRequest(BaseModel):
+    """娓呯┖缁熻璇锋眰妯″瀷"""
+    group_id: str
+
+
+@router.get("/group-stats/{group_id}", response_model=GroupStatsResponse)
+async def get_group_stats(group_id: str):
+    """
+    鑾峰彇鎸囧畾缇ょ粍鐨勭粺璁′俊鎭�
+
+    Args:
+        group_id: 缇ょ粍ID
+
+    Returns:
+        缇ょ粍缁熻淇℃伅
+    """
+    try:
+        stats_summary = group_stats_service.get_group_stats_summary(group_id)
+        
+        return GroupStatsResponse(
+            success=True,
+            message=f"鑾峰彇缇ょ粍缁熻淇℃伅鎴愬姛: {group_id}",
+            data=stats_summary
+        )
+
+    except Exception as e:
+        logger.error(f"鑾峰彇缇ょ粍缁熻淇℃伅澶辫触: group_id={group_id}, error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"鑾峰彇缇ょ粍缁熻淇℃伅澶辫触: {str(e)}")
+
+
+@router.get("/group-stats/{group_id}/most-active", response_model=GroupStatsResponse)
+async def get_most_active_user(group_id: str):
+    """
+    鑾峰彇鎸囧畾缇ょ粍涓彂瑷�娆℃暟鏈�澶氱殑鐢ㄦ埛鏄电О
+
+    Args:
+        group_id: 缇ょ粍ID
+
+    Returns:
+        鏈�娲昏穬鐢ㄦ埛淇℃伅
+    """
+    try:
+        most_active_nickname = group_stats_service.get_most_active_user_nickname(group_id)
+        message_stats = group_stats_service.get_group_message_stats(group_id)
+        
+        # 鎵惧埌鏈�娲昏穬鐢ㄦ埛鐨勮缁嗕俊鎭�
+        most_active_user_id = None
+        max_count = 0
+        
+        for user_id, count in message_stats.items():
+            if count > max_count:
+                max_count = count
+                most_active_user_id = user_id
+        
+        data = {
+            "group_id": group_id,
+            "most_active_nickname": most_active_nickname,
+            "most_active_user_id": most_active_user_id,
+            "message_count": max_count,
+            "total_users": len([count for count in message_stats.values() if count > 0])
+        }
+        
+        return GroupStatsResponse(
+            success=True,
+            message=f"鑾峰彇鏈�娲昏穬鐢ㄦ埛鎴愬姛: {group_id}",
+            data=data
+        )
+
+    except Exception as e:
+        logger.error(f"鑾峰彇鏈�娲昏穬鐢ㄦ埛澶辫触: group_id={group_id}, error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"鑾峰彇鏈�娲昏穬鐢ㄦ埛澶辫触: {str(e)}")
+
+
+@router.post("/group-stats/clear", response_model=GroupStatsResponse)
+async def clear_group_stats(request: ClearStatsRequest):
+    """
+    娓呯┖鎸囧畾缇ょ粍鐨勭粺璁℃暟鎹�
+
+    Args:
+        request: 娓呯┖缁熻璇锋眰
+
+    Returns:
+        鎿嶄綔缁撴灉
+    """
+    try:
+        success = group_stats_service.clear_group_stats(request.group_id)
+        
+        if success:
+            return GroupStatsResponse(
+                success=True,
+                message=f"缇ょ粍缁熻鏁版嵁宸叉竻绌�: {request.group_id}",
+                data={"group_id": request.group_id, "cleared": True}
+            )
+        else:
+            raise HTTPException(status_code=400, detail=f"娓呯┖缇ょ粍缁熻鏁版嵁澶辫触: {request.group_id}")
+
+    except HTTPException:
+        raise
+    except Exception as e:
+        logger.error(f"娓呯┖缇ょ粍缁熻鏁版嵁澶辫触: group_id={request.group_id}, error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"娓呯┖缇ょ粍缁熻鏁版嵁澶辫触: {str(e)}")
+
+
+@router.get("/group-stats", response_model=GroupStatsResponse)
+async def get_all_groups_overview():
+    """
+    鑾峰彇鎵�鏈夌兢缁勭殑缁熻姒傝
+
+    Returns:
+        鎵�鏈夌兢缁勭殑缁熻姒傝
+    """
+    try:
+        # 杩欓噷鍙互鎵╁睍涓鸿幏鍙栨墍鏈夌兢缁勭殑缁熻淇℃伅
+        # 鐩墠杩斿洖鍩烘湰淇℃伅
+        data = {
+            "message": "缇ょ粍缁熻鍔熻兘宸插惎鐢�",
+            "endpoints": {
+                "get_group_stats": "/api/v1/group-stats/{group_id}",
+                "get_most_active": "/api/v1/group-stats/{group_id}/most-active",
+                "clear_stats": "/api/v1/group-stats/clear"
+            }
+        }
+        
+        return GroupStatsResponse(
+            success=True,
+            message="鑾峰彇缇ょ粍缁熻姒傝鎴愬姛",
+            data=data
+        )
+
+    except Exception as e:
+        logger.error(f"鑾峰彇缇ょ粍缁熻姒傝澶辫触: error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"鑾峰彇缇ょ粍缁熻姒傝澶辫触: {str(e)}")
diff --git a/app/api/silence_mode.py b/app/api/silence_mode.py
new file mode 100644
index 0000000..e1c6c0f
--- /dev/null
+++ b/app/api/silence_mode.py
@@ -0,0 +1,191 @@
+"""
+闈欓粯妯″紡绠$悊API
+"""
+
+from fastapi import APIRouter, HTTPException, Query
+from pydantic import BaseModel
+from typing import Optional
+from loguru import logger
+
+from app.services.silence_service import silence_service
+from config import settings
+
+
+router = APIRouter()
+
+
+class SilenceModeResponse(BaseModel):
+    """闈欓粯妯″紡鍝嶅簲妯″瀷"""
+    success: bool
+    message: str
+    data: Optional[dict] = None
+
+
+class ActivateSilenceRequest(BaseModel):
+    """婵�娲婚潤榛樻ā寮忚姹傛ā鍨�"""
+    group_id: str
+
+
+@router.get("/silence-mode/status", response_model=SilenceModeResponse)
+async def get_silence_mode_status(group_id: Optional[str] = None):
+    """
+    鑾峰彇闈欓粯妯″紡鐘舵��
+
+    Args:
+        group_id: 缇ょ粍ID锛屽鏋滀笉鎻愪緵鍒欒繑鍥炲叏灞�鐘舵�佹瑙�
+
+    Returns:
+        闈欓粯妯″紡鐘舵�佷俊鎭�
+    """
+    try:
+        status = silence_service.get_silence_status(group_id)
+
+        message = "鑾峰彇闈欓粯妯″紡鐘舵�佹垚鍔�"
+        if group_id:
+            message = f"鑾峰彇缇ょ粍闈欓粯妯″紡鐘舵�佹垚鍔�: {group_id}"
+
+        return SilenceModeResponse(
+            success=True,
+            message=message,
+            data=status
+        )
+
+    except Exception as e:
+        logger.error(f"鑾峰彇闈欓粯妯″紡鐘舵�佸け璐�: group_id={group_id}, error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"鑾峰彇闈欓粯妯″紡鐘舵�佸け璐�: {str(e)}")
+
+
+@router.post("/silence-mode/activate", response_model=SilenceModeResponse)
+async def activate_silence_mode(request: ActivateSilenceRequest):
+    """
+    鎵嬪姩婵�娲绘寚瀹氱兢缁勭殑闈欓粯妯″紡
+
+    Args:
+        request: 婵�娲昏姹傦紝鍖呭惈缇ょ粍ID
+
+    Returns:
+        鎿嶄綔缁撴灉
+    """
+    try:
+        if not settings.silence_mode_enabled:
+            raise HTTPException(status_code=400, detail="闈欓粯妯″紡鍔熻兘宸茬鐢�")
+
+        success = silence_service.activate_silence_mode(request.group_id)
+
+        if success:
+            status = silence_service.get_silence_status(request.group_id)
+            return SilenceModeResponse(
+                success=True,
+                message=f"缇ょ粍闈欓粯妯″紡宸叉縺娲�: {request.group_id}锛屾寔缁椂闂�: {settings.silence_duration_minutes} 鍒嗛挓",
+                data=status
+            )
+        else:
+            raise HTTPException(status_code=400, detail=f"婵�娲荤兢缁勯潤榛樻ā寮忓け璐�: {request.group_id}")
+
+    except HTTPException:
+        raise
+    except Exception as e:
+        logger.error(f"婵�娲荤兢缁勯潤榛樻ā寮忓け璐�: group_id={request.group_id}, error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"婵�娲荤兢缁勯潤榛樻ā寮忓け璐�: {str(e)}")
+
+
+class ExtendSilenceRequest(BaseModel):
+    """寤堕暱闈欓粯妯″紡璇锋眰妯″瀷"""
+    group_id: str
+
+
+@router.post("/silence-mode/extend", response_model=SilenceModeResponse)
+async def extend_silence_mode(request: ExtendSilenceRequest):
+    """
+    寤堕暱鎸囧畾缇ょ粍鐨勯潤榛樻ā寮忔椂闂�
+
+    Args:
+        request: 寤堕暱璇锋眰锛屽寘鍚兢缁処D
+
+    Returns:
+        鎿嶄綔缁撴灉
+    """
+    try:
+        if not settings.silence_mode_enabled:
+            raise HTTPException(status_code=400, detail="闈欓粯妯″紡鍔熻兘宸茬鐢�")
+
+        success = silence_service.extend_silence_mode(request.group_id)
+
+        if success:
+            status = silence_service.get_silence_status(request.group_id)
+            return SilenceModeResponse(
+                success=True,
+                message=f"缇ょ粍闈欓粯妯″紡鏃堕棿宸插埛鏂�: {request.group_id}锛屾寔缁椂闂�: {settings.silence_duration_minutes} 鍒嗛挓",
+                data=status
+            )
+        else:
+            raise HTTPException(status_code=400, detail=f"寤堕暱缇ょ粍闈欓粯妯″紡澶辫触: {request.group_id}")
+
+    except HTTPException:
+        raise
+    except Exception as e:
+        logger.error(f"寤堕暱缇ょ粍闈欓粯妯″紡澶辫触: group_id={request.group_id}, error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"寤堕暱缇ょ粍闈欓粯妯″紡澶辫触: {str(e)}")
+
+
+class DeactivateSilenceRequest(BaseModel):
+    """鍋滅敤闈欓粯妯″紡璇锋眰妯″瀷"""
+    group_id: Optional[str] = None  # 濡傛灉涓篘one鍒欏仠鐢ㄦ墍鏈夌兢缁�
+
+
+@router.post("/silence-mode/deactivate", response_model=SilenceModeResponse)
+async def deactivate_silence_mode(request: DeactivateSilenceRequest):
+    """
+    鎵嬪姩鍋滅敤闈欓粯妯″紡
+
+    Args:
+        request: 鍋滅敤璇锋眰锛実roup_id涓篘one鏃跺仠鐢ㄦ墍鏈夌兢缁�
+
+    Returns:
+        鎿嶄綔缁撴灉
+    """
+    try:
+        success = silence_service.deactivate_silence_mode(request.group_id)
+
+        if success:
+            status = silence_service.get_silence_status()
+            message = "鎵�鏈夌兢缁勭殑闈欓粯妯″紡宸插仠鐢�" if not request.group_id else f"缇ょ粍闈欓粯妯″紡宸插仠鐢�: {request.group_id}"
+
+            return SilenceModeResponse(
+                success=True,
+                message=message,
+                data=status
+            )
+        else:
+            detail = "鍋滅敤闈欓粯妯″紡澶辫触" if not request.group_id else f"鍋滅敤缇ょ粍闈欓粯妯″紡澶辫触: {request.group_id}"
+            raise HTTPException(status_code=400, detail=detail)
+
+    except HTTPException:
+        raise
+    except Exception as e:
+        logger.error(f"鍋滅敤闈欓粯妯″紡澶辫触: group_id={request.group_id}, error={str(e)}")
+        raise HTTPException(status_code=500, detail=f"鍋滅敤闈欓粯妯″紡澶辫触: {str(e)}")
+
+
+@router.get("/silence-mode/config")
+async def get_silence_mode_config():
+    """
+    鑾峰彇闈欓粯妯″紡閰嶇疆淇℃伅
+    
+    Returns:
+        閰嶇疆淇℃伅
+    """
+    try:
+        return {
+            "success": True,
+            "data": {
+                "enabled": settings.silence_mode_enabled,
+                "duration_minutes": settings.silence_duration_minutes,
+                "description": "缇ょ粍闈欓粯妯″紡鍔熻兘锛氬綋濂藉弸娑堟伅琚拷鐣ユ椂锛岃嚜鍔ㄦ縺娲昏缇ょ粍鐨勯潤榛樻ā寮忥紝鍦ㄦ寚瀹氭椂闂村唴涓嶅鐞嗚缇ょ粍鐨勬秷鎭�"
+            },
+            "message": "鑾峰彇闈欓粯妯″紡閰嶇疆淇℃伅鎴愬姛"
+        }
+        
+    except Exception as e:
+        logger.error(f"鑾峰彇闈欓粯妯″紡閰嶇疆淇℃伅澶辫触: {str(e)}")
+        raise HTTPException(status_code=500, detail=f"鑾峰彇闈欓粯妯″紡閰嶇疆淇℃伅澶辫触: {str(e)}")
diff --git a/app/services/dify_client.py b/app/services/dify_client.py
index e7ab10e..0e6bbda 100644
--- a/app/services/dify_client.py
+++ b/app/services/dify_client.py
@@ -28,6 +28,7 @@
         user: str,
         conversation_id: Optional[str] = None,
         max_retries: int = None,
+        nick_name: Optional[str] = None,
     ) -> Optional[Dict[str, Any]]:
         """
         鍙戦�佸璇濇秷鎭紙闈炴祦寮忔ā寮忥級
@@ -37,6 +38,7 @@
             user: 鐢ㄦ埛鏍囪瘑
             conversation_id: 浼氳瘽ID锛堝彲閫夛級
             max_retries: 鏈�澶ч噸璇曟鏁�
+            nick_name: 鏄电О锛堝彲閫夛紝鐢ㄤ簬浼犻�掔粰dify锛�
 
         Returns:
             鍝嶅簲鏁版嵁瀛楀吀锛屽け璐ヨ繑鍥濶one
@@ -45,11 +47,17 @@
             max_retries = settings.max_retry_count
 
         url = f"{self.base_url}/chat-messages"
+
+        # 鏋勫缓inputs鍙傛暟
+        inputs = {}
+        if nick_name:
+            inputs["nick_name"] = nick_name
+
         payload = {
             "query": query,
             "response_mode": "blocking",  # 浣跨敤闃诲妯″紡锛堥潪娴佸紡锛�
             "user": user,
-            "inputs": {},
+            "inputs": inputs,
         }
 
         # 濡傛灉鏈変細璇滻D锛屾坊鍔犲埌璇锋眰涓�
@@ -105,6 +113,7 @@
         user: str,
         conversation_id: Optional[str] = None,
         max_retries: int = None,
+        nick_name: Optional[str] = None,
     ) -> Optional[Dict[str, Any]]:
         """
         鍙戦�佸璇濇秷鎭紙娴佸紡妯″紡锛�
@@ -114,6 +123,7 @@
             user: 鐢ㄦ埛鏍囪瘑
             conversation_id: 浼氳瘽ID锛堝彲閫夛級
             max_retries: 鏈�澶ч噸璇曟鏁�
+            nick_name: 鏄电О锛堝彲閫夛紝鐢ㄤ簬浼犻�掔粰dify锛�
 
         Returns:
             瀹屾暣鐨勫搷搴旀暟鎹瓧鍏革紝澶辫触杩斿洖None
@@ -122,11 +132,17 @@
             max_retries = settings.max_retry_count
 
         url = f"{self.base_url}/chat-messages"
+
+        # 鏋勫缓inputs鍙傛暟
+        inputs = {}
+        if nick_name:
+            inputs["nick_name"] = nick_name
+
         payload = {
             "query": query,
             "response_mode": "streaming",  # 浣跨敤娴佸紡妯″紡
             "user": user,
-            "inputs": {},
+            "inputs": inputs,
         }
 
         # 濡傛灉鏈変細璇滻D锛屾坊鍔犲埌璇锋眰涓�
@@ -384,6 +400,7 @@
         conversation_id: Optional[str] = None,
         max_retries: int = None,
         force_streaming: Optional[bool] = None,
+        nick_name: Optional[str] = None,
     ) -> Optional[Dict[str, Any]]:
         """
         鍙戦�佸璇濇秷鎭紙鏍规嵁閰嶇疆閫夋嫨妯″紡锛�
@@ -394,6 +411,7 @@
             conversation_id: 浼氳瘽ID锛堝彲閫夛級
             max_retries: 鏈�澶ч噸璇曟鏁�
             force_streaming: 寮哄埗浣跨敤娴佸紡妯″紡锛堝彲閫夛紝瑕嗙洊閰嶇疆锛�
+            nick_name: 鏄电О锛堝彲閫夛紝鐢ㄤ簬浼犻�掔粰dify锛�
 
         Returns:
             鍝嶅簲鏁版嵁瀛楀吀锛屽け璐ヨ繑鍥濶one
@@ -403,10 +421,10 @@
 
         if use_streaming:
             logger.info(f"浣跨敤娴佸紡妯″紡鍙戦�佹秷鎭�: user={user}")
-            return self.send_chat_message_stream(query, user, conversation_id, max_retries)
+            return self.send_chat_message_stream(query, user, conversation_id, max_retries, nick_name)
         else:
             logger.info(f"浣跨敤闃诲妯″紡鍙戦�佹秷鎭�: user={user}")
-            return self.send_chat_message(query, user, conversation_id, max_retries)
+            return self.send_chat_message(query, user, conversation_id, max_retries, nick_name)
 
     def get_conversation_messages(
         self, conversation_id: str, user: str
diff --git a/app/services/ecloud_client.py b/app/services/ecloud_client.py
index 42495c3..b8a64ef 100644
--- a/app/services/ecloud_client.py
+++ b/app/services/ecloud_client.py
@@ -20,6 +20,27 @@
         self.session = requests.Session()
         self.session.headers.update(self.headers)
 
+    def _filter_keywords(self, content: str) -> str:
+        """
+        杩囨护娑堟伅鍐呭涓殑鍏抽敭璇�
+
+        Args:
+            content: 鍘熷娑堟伅鍐呭
+
+        Returns:
+            杩囨护鍚庣殑娑堟伅鍐呭
+        """
+        if not settings.keyword_filter_enabled or not settings.keyword_filter_keywords:
+            return content
+
+        filtered_content = content
+        for keyword in settings.keyword_filter_keywords:
+            if keyword in filtered_content:
+                filtered_content = filtered_content.replace(keyword, "")
+                logger.info(f"杩囨护鍏抽敭璇�: {keyword}")
+
+        return filtered_content
+
     def get_contact_info(self, w_id: str, wc_id: str) -> Optional[Dict[str, Any]]:
         """
         鑾峰彇鑱旂郴浜轰俊鎭�
@@ -84,18 +105,21 @@
         Returns:
             鍙戦�佹垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
         """
+        # 杩囨护鍏抽敭璇�
+        filtered_content = self._filter_keywords(content)
+
         if max_retries is None:
             from config import settings
             max_retries = settings.max_retry_count
-            
+
         retry_count = 0
         while retry_count <= max_retries:
             try:
                 url = f"{self.base_url}/sendText"
-                payload = {"wId": w_id, "wcId": wc_id, "content": content}
+                payload = {"wId": w_id, "wcId": wc_id, "content": filtered_content}
 
                 logger.info(
-                    f"鍙戦�佹枃鏈秷鎭�: wId={w_id}, wcId={wc_id}, content_length={len(content)}, retry={retry_count}"
+                    f"鍙戦�佹枃鏈秷鎭�: wId={w_id}, wcId={wc_id}, content_length={len(filtered_content)}, retry={retry_count}"
                 )
 
                 response = self.session.post(url, json=payload, timeout=30)
@@ -141,7 +165,9 @@
         Returns:
             鍙戦�佹垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
         """
-        return self.send_text_message(w_id, group_id, content)
+        # 杩囨护鍏抽敭璇�
+        filtered_content = self._filter_keywords(content)
+        return self.send_text_message(w_id, group_id, filtered_content)
 
     def init_address_list(self, w_id: str) -> bool:
         """
@@ -238,6 +264,9 @@
         Returns:
             鍙戦�佹垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
         """
+        # 杩囨护鍏抽敭璇�
+        filtered_content = self._filter_keywords(content)
+
         if max_retries is None:
             from config import settings
             max_retries = settings.max_retry_count
@@ -251,12 +280,12 @@
                 payload = {
                     "wId": w_id,
                     "wcId": wc_id,
-                    "content": content,
+                    "content": filtered_content,
                     "at": at_str
                 }
 
                 logger.info(
-                    f"鍙戦�佺兢鑱夽娑堟伅: wId={w_id}, wcId={wc_id}, at={at_str}, content_length={len(content)}, retry={retry_count}"
+                    f"鍙戦�佺兢鑱夽娑堟伅: wId={w_id}, wcId={wc_id}, at={at_str}, content_length={len(filtered_content)}, retry={retry_count}"
                 )
 
                 response = self.session.post(url, json=payload, timeout=30)
@@ -290,6 +319,47 @@
         )
         return False
 
+    def query_online_wechat_list(self) -> Optional[List[Dict[str, str]]]:
+        """
+        鏌ヨ璐﹀彿涓湪绾跨殑寰俊鍒楄〃
+
+        Returns:
+            鍦ㄧ嚎寰俊鍒楄〃锛屾瘡涓厓绱犲寘鍚玾cId鍜寃Id锛屽け璐ヨ繑鍥濶one
+            杩斿洖鏍煎紡: [
+                {
+                    "wcId": "wxid_i6qsbbjenju2",
+                    "wId": "72223018-7f2a-4f4f-bfa3-26e47dbd61"
+                }
+            ]
+        """
+        try:
+            url = f"{self.base_url}/queryLoginWx"
+            payload = {}
+
+            logger.info("鏌ヨ鍦ㄧ嚎寰俊鍒楄〃")
+
+            response = self.session.post(url, json=payload, timeout=30)
+            response.raise_for_status()
+
+            result = response.json()
+
+            if result.get("code") == "1000":
+                online_list = result.get("data", [])
+                logger.info(f"鎴愬姛鏌ヨ鍦ㄧ嚎寰俊鍒楄〃: count={len(online_list)}")
+                return online_list
+            else:
+                logger.warning(
+                    f"鏌ヨ鍦ㄧ嚎寰俊鍒楄〃澶辫触: code={result.get('code')}, message={result.get('message')}"
+                )
+                return []
+
+        except requests.exceptions.RequestException as e:
+            logger.error(f"鏌ヨ鍦ㄧ嚎寰俊鍒楄〃缃戠粶閿欒: error={str(e)}")
+            return None
+        except Exception as e:
+            logger.error(f"鏌ヨ鍦ㄧ嚎寰俊鍒楄〃寮傚父: error={str(e)}")
+            return None
+
 
 # 鍏ㄥ眬E浜戠瀹跺鎴风瀹炰緥
 ecloud_client = ECloudClient()
diff --git a/app/services/email_service.py b/app/services/email_service.py
new file mode 100644
index 0000000..8f7b8d8
--- /dev/null
+++ b/app/services/email_service.py
@@ -0,0 +1,149 @@
+"""
+閭欢鍙戦�佹湇鍔�
+"""
+
+import smtplib
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+from typing import List, Optional
+from loguru import logger
+from config import settings
+
+
+class EmailService:
+    """閭欢鍙戦�佹湇鍔�"""
+
+    def __init__(self):
+        self.smtp_server = settings.email_smtp_server
+        self.smtp_port = settings.email_smtp_port
+        self.username = settings.email_smtp_username
+        self.password = settings.email_smtp_password
+        self.from_email = settings.email_from_email
+
+    def send_email(self, to_emails: List[str], subject: str, content: str) -> bool:
+        """
+        鍙戦�侀偖浠�
+
+        Args:
+            to_emails: 鏀朵欢浜洪偖绠卞垪琛�
+            subject: 閭欢涓婚
+            content: 閭欢鍐呭
+
+        Returns:
+            鍙戦�佹垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
+        """
+        if not settings.email_enabled:
+            logger.info("閭欢鍙戦�佸姛鑳藉凡绂佺敤")
+            return True
+
+        if not to_emails:
+            logger.warning("鏀朵欢浜洪偖绠卞垪琛ㄤ负绌�")
+            return False
+
+        try:
+            # 鍒涘缓閭欢瀵硅薄
+            msg = MIMEMultipart()
+            msg["From"] = self.from_email
+            msg["To"] = ", ".join(to_emails)
+            msg["Subject"] = subject
+
+            # 娣诲姞閭欢鍐呭
+            msg.attach(MIMEText(content, "plain", "utf-8"))
+
+            # 杩炴帴SMTP鏈嶅姟鍣ㄥ苟鍙戦�侀偖浠�
+            server = None
+            email_sent = False
+
+            try:
+                server = smtplib.SMTP(self.smtp_server, self.smtp_port)
+                server.starttls()  # 鍚敤TLS鍔犲瘑
+                server.login(self.username, self.password)
+
+                # 鍙戦�侀偖浠�
+                text = msg.as_string()
+                server.sendmail(self.from_email, to_emails, text)
+                email_sent = True
+
+                logger.info(f"閭欢鍙戦�佹垚鍔�: to={to_emails}, subject={subject}")
+
+            finally:
+                # 瀹夊叏鍏抽棴杩炴帴锛屽拷鐣ュ叧闂椂鐨勫紓甯�
+                if server:
+                    try:
+                        server.quit()
+                    except Exception:
+                        # 蹇界暐鍏抽棴杩炴帴鏃剁殑寮傚父锛屽洜涓洪偖浠跺彲鑳藉凡缁忓彂閫佹垚鍔�
+                        pass
+
+            return email_sent
+
+        except smtplib.SMTPAuthenticationError as e:
+            logger.error(f"閭欢鍙戦�佽璇佸け璐�: error={str(e)}")
+            return False
+        except smtplib.SMTPException as e:
+            logger.error(f"閭欢鍙戦�丼MTP閿欒: error={str(e)}")
+            return False
+        except Exception as e:
+            logger.error(f"閭欢鍙戦�佸紓甯�: error={str(e)}")
+            return False
+
+    def send_notification(self, subject: str, content: str) -> bool:
+        """
+        鍙戦�侀�氱煡閭欢鍒伴厤缃殑鏀朵欢浜哄垪琛�
+
+        Args:
+            subject: 閭欢涓婚
+            content: 閭欢鍐呭
+
+        Returns:
+            鍙戦�佹垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
+        """
+        return self.send_email(settings.email_to_emails, subject, content)
+
+    def test_connection(self) -> bool:
+        """
+        娴嬭瘯閭欢鏈嶅姟鍣ㄨ繛鎺�
+
+        Returns:
+            杩炴帴鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        if not settings.email_enabled:
+            logger.info("閭欢鍙戦�佸姛鑳藉凡绂佺敤")
+            return True
+
+        try:
+            server = None
+            connection_success = False
+
+            try:
+                server = smtplib.SMTP(self.smtp_server, self.smtp_port)
+                server.starttls()
+                server.login(self.username, self.password)
+                connection_success = True
+
+                logger.info("閭欢鏈嶅姟鍣ㄨ繛鎺ユ祴璇曟垚鍔�")
+
+            finally:
+                # 瀹夊叏鍏抽棴杩炴帴锛屽拷鐣ュ叧闂椂鐨勫紓甯�
+                if server:
+                    try:
+                        server.quit()
+                    except Exception:
+                        # 蹇界暐鍏抽棴杩炴帴鏃剁殑寮傚父
+                        pass
+
+            return connection_success
+
+        except smtplib.SMTPAuthenticationError as e:
+            logger.error(f"閭欢鏈嶅姟鍣ㄨ璇佸け璐�: error={str(e)}")
+            return False
+        except smtplib.SMTPException as e:
+            logger.error(f"閭欢鏈嶅姟鍣ㄨ繛鎺MTP閿欒: error={str(e)}")
+            return False
+        except Exception as e:
+            logger.error(f"閭欢鏈嶅姟鍣ㄨ繛鎺ュ紓甯�: error={str(e)}")
+            return False
+
+
+# 鍏ㄥ眬閭欢鏈嶅姟瀹炰緥
+email_service = EmailService()
diff --git a/app/services/group_stats_service.py b/app/services/group_stats_service.py
new file mode 100644
index 0000000..1feb2be
--- /dev/null
+++ b/app/services/group_stats_service.py
@@ -0,0 +1,251 @@
+"""
+缇ょ粍缁熻鏈嶅姟
+鐢ㄤ簬缁熻缇ょ粍涓ソ鍙嬬殑鍙戣█娆℃暟
+"""
+
+import time
+from typing import Optional, Dict, List, Tuple
+from loguru import logger
+from app.services.redis_queue import redis_queue
+from app.models.contact import Contact
+from app.models.database import get_db
+from config import settings
+
+
+class GroupStatsService:
+    """缇ょ粍缁熻鏈嶅姟"""
+
+    def __init__(self):
+        self.stats_key_prefix = "group_stats:"
+        self.stats_expiry = 24 * 60 * 60  # 24灏忔椂杩囨湡
+
+    def _get_group_stats_key(self, group_id: str) -> str:
+        """
+        鑾峰彇缇ょ粍缁熻鐨凴edis閿悕
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            Redis閿悕
+        """
+        return f"{self.stats_key_prefix}{group_id}"
+
+    def increment_user_message_count(self, group_id: str, user_id: str) -> bool:
+        """
+        澧炲姞鐢ㄦ埛鍦ㄧ兢缁勪腑鐨勫彂瑷�娆℃暟
+
+        Args:
+            group_id: 缇ょ粍ID
+            user_id: 鐢ㄦ埛ID
+
+        Returns:
+            鎿嶄綔鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        try:
+            stats_key = self._get_group_stats_key(group_id)
+
+            # 浣跨敤Redis鐨凥INCRBY鍛戒护澧炲姞璁℃暟
+            redis_queue.redis_client.hincrby(stats_key, user_id, 1)
+
+            # 璁剧疆杩囨湡鏃堕棿
+            redis_queue.redis_client.expire(stats_key, self.stats_expiry)
+
+            logger.debug(f"鐢ㄦ埛鍙戣█娆℃暟宸插鍔�: group={group_id}, user={user_id}")
+            return True
+
+        except Exception as e:
+            logger.error(
+                f"澧炲姞鐢ㄦ埛鍙戣█娆℃暟澶辫触: group={group_id}, user={user_id}, error={str(e)}"
+            )
+            return False
+
+    def get_group_message_stats(self, group_id: str) -> Dict[str, int]:
+        """
+        鑾峰彇缇ょ粍涓墍鏈夌敤鎴风殑鍙戣█娆℃暟缁熻
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            鐢ㄦ埛ID鍒板彂瑷�娆℃暟鐨勬槧灏勫瓧鍏�
+        """
+        try:
+            stats_key = self._get_group_stats_key(group_id)
+            stats = redis_queue.redis_client.hgetall(stats_key)
+
+            # 灏嗗瓧鑺傚瓧绗︿覆杞崲涓烘櫘閫氬瓧绗︿覆锛屽苟杞崲璁℃暟涓烘暣鏁�
+            result = {}
+            for user_id, count in stats.items():
+                if isinstance(user_id, bytes):
+                    user_id = user_id.decode("utf-8")
+                if isinstance(count, bytes):
+                    count = count.decode("utf-8")
+                result[user_id] = int(count)
+
+            logger.debug(f"鑾峰彇缇ょ粍鍙戣█缁熻: group={group_id}, stats={result}")
+            return result
+
+        except Exception as e:
+            logger.error(f"鑾峰彇缇ょ粍鍙戣█缁熻澶辫触: group={group_id}, error={str(e)}")
+            return {}
+
+    def get_most_active_user_nickname(self, group_id: str) -> str:
+        """
+        鑾峰彇缇ょ粍涓彂瑷�娆℃暟鏈�澶氱殑鐢ㄦ埛鏄电О
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            鍙戣█娆℃暟鏈�澶氱殑鐢ㄦ埛鏄电О锛屽鏋滄病鏈夋壘鍒版垨鍙戣█娆℃暟閮界浉鍚屾垨閮戒负0锛屽垯杩斿洖榛樿瀹㈡湇鍚嶇О
+        """
+        try:
+            # 鑾峰彇缇ょ粍鍙戣█缁熻
+            stats = self.get_group_message_stats(group_id)
+
+            if not stats:
+                logger.info(f"缇ょ粍鏃犲彂瑷�缁熻鏁版嵁锛屼娇鐢ㄩ粯璁ゅ鏈嶅悕绉�: group={group_id}")
+                return settings.customer_service_default_name
+
+            # 杩囨护鎺夊彂瑷�娆℃暟涓�0鐨勭敤鎴�
+            active_stats = {
+                user_id: count for user_id, count in stats.items() if count > 0
+            }
+
+            if not active_stats:
+                logger.info(f"缇ょ粍鏃犳湁鏁堝彂瑷�鏁版嵁锛屼娇鐢ㄩ粯璁ゅ鏈嶅悕绉�: group={group_id}")
+                return settings.customer_service_default_name
+
+            # 妫�鏌ユ槸鍚︽墍鏈夌敤鎴峰彂瑷�娆℃暟閮界浉鍚�
+            counts = list(active_stats.values())
+            if len(set(counts)) == 1:
+                logger.info(
+                    f"缇ょ粍鎵�鏈夌敤鎴峰彂瑷�娆℃暟鐩稿悓锛屼娇鐢ㄩ粯璁ゅ鏈嶅悕绉�: group={group_id}"
+                )
+                return settings.customer_service_default_name
+
+            # 鎵惧埌鍙戣█娆℃暟鏈�澶氱殑鐢ㄦ埛
+            most_active_user_id = max(active_stats, key=active_stats.get)
+            max_count = active_stats[most_active_user_id]
+
+            logger.info(
+                f"鎵惧埌鏈�娲昏穬鐢ㄦ埛: group={group_id}, user={most_active_user_id}, count={max_count}"
+            )
+
+            # 鏍规嵁鐢ㄦ埛ID鏌ユ壘鏄电О
+            nickname = self._get_user_nickname(most_active_user_id)
+
+            if nickname:
+                logger.info(
+                    f"鑾峰彇鍒版渶娲昏穬鐢ㄦ埛鏄电О: group={group_id}, user={most_active_user_id}, nickname={nickname}"
+                )
+                return nickname
+            else:
+                logger.warning(
+                    f"鏈壘鍒版渶娲昏穬鐢ㄦ埛鏄电О锛屼娇鐢ㄩ粯璁ゅ鏈嶅悕绉�: group={group_id}, user={most_active_user_id}"
+                )
+                return settings.customer_service_default_name
+
+        except Exception as e:
+            logger.error(f"鑾峰彇鏈�娲昏穬鐢ㄦ埛鏄电О澶辫触: group={group_id}, error={str(e)}")
+            return settings.customer_service_default_name
+
+    def _get_user_nickname(self, user_id: str) -> Optional[str]:
+        """
+        鏍规嵁鐢ㄦ埛ID鑾峰彇鏄电О
+
+        Args:
+            user_id: 鐢ㄦ埛ID
+
+        Returns:
+            鐢ㄦ埛鏄电О锛屽鏋滄湭鎵惧埌杩斿洖None
+        """
+        try:
+            with next(get_db()) as db:
+                contact = db.query(Contact).filter(Contact.wc_id == user_id).first()
+                if contact and contact.nick_name:
+                    return contact.nick_name
+                else:
+                    logger.warning(f"鏈壘鍒扮敤鎴锋樀绉�: user_id={user_id}")
+                    return None
+        except Exception as e:
+            logger.error(f"鏌ヨ鐢ㄦ埛鏄电О寮傚父: user_id={user_id}, error={str(e)}")
+            return None
+
+    def get_group_stats_summary(self, group_id: str) -> Dict:
+        """
+        鑾峰彇缇ょ粍缁熻鎽樿淇℃伅
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            鍖呭惈缁熻鎽樿鐨勫瓧鍏�
+        """
+        try:
+            stats = self.get_group_message_stats(group_id)
+            most_active_nickname = self.get_most_active_user_nickname(group_id)
+
+            # 璁$畻鎬诲彂瑷�娆℃暟
+            total_messages = sum(stats.values())
+
+            # 鑾峰彇娲昏穬鐢ㄦ埛鏁伴噺锛堝彂瑷�娆℃暟>0锛�
+            active_users = len([count for count in stats.values() if count > 0])
+
+            # 鏋勫缓鐢ㄦ埛鏄电О缁熻
+            user_stats = []
+            for user_id, count in stats.items():
+                if count > 0:
+                    nickname = self._get_user_nickname(user_id)
+                    user_stats.append(
+                        {
+                            "user_id": user_id,
+                            "nickname": nickname or "鏈煡鐢ㄦ埛",
+                            "message_count": count,
+                        }
+                    )
+
+            # 鎸夊彂瑷�娆℃暟鎺掑簭
+            user_stats.sort(key=lambda x: x["message_count"], reverse=True)
+
+            return {
+                "group_id": group_id,
+                "total_messages": total_messages,
+                "active_users": active_users,
+                "most_active_nickname": most_active_nickname,
+                "user_stats": user_stats,
+            }
+
+        except Exception as e:
+            logger.error(f"鑾峰彇缇ょ粍缁熻鎽樿澶辫触: group={group_id}, error={str(e)}")
+            return {
+                "group_id": group_id,
+                "total_messages": 0,
+                "active_users": 0,
+                "most_active_nickname": settings.customer_service_default_name,
+                "user_stats": [],
+            }
+
+    def clear_group_stats(self, group_id: str) -> bool:
+        """
+        娓呯┖鎸囧畾缇ょ粍鐨勭粺璁℃暟鎹�
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            鎿嶄綔鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        try:
+            stats_key = self._get_group_stats_key(group_id)
+            redis_queue.redis_client.delete(stats_key)
+            logger.info(f"宸叉竻绌虹兢缁勭粺璁℃暟鎹�: group={group_id}")
+            return True
+        except Exception as e:
+            logger.error(f"娓呯┖缇ょ粍缁熻鏁版嵁澶辫触: group={group_id}, error={str(e)}")
+            return False
+
+
+# 鍏ㄥ眬缇ょ粍缁熻鏈嶅姟瀹炰緥
+group_stats_service = GroupStatsService()
diff --git a/app/services/message_processor.py b/app/services/message_processor.py
index e70555a..11b1bf6 100644
--- a/app/services/message_processor.py
+++ b/app/services/message_processor.py
@@ -17,6 +17,8 @@
 from app.services.ecloud_client import ecloud_client
 from app.services.dify_client import dify_client
 from app.services.friend_ignore_service import friend_ignore_service
+from app.services.silence_service import silence_service
+from app.services.group_stats_service import group_stats_service
 from config import settings
 
 
@@ -102,10 +104,34 @@
             logger.warning(f"娑堟伅缂哄皯蹇呰瀛楁: data={data}")
             return False
 
-        # 妫�鏌ュ彂閫佽�呮槸鍚﹀湪濂藉弸蹇界暐鍒楄〃涓�
+        # 鑾峰彇鐢ㄦ埛鍜岀兢缁勪俊鎭�
         from_user = data.get("fromUser")
-        if friend_ignore_service.is_friend_ignored(from_user):
-            logger.info(f"蹇界暐濂藉弸鍙戦�佺殑娑堟伅: fromUser={from_user}")
+        from_group = data.get("fromGroup")
+
+        # 妫�鏌ュ彂閫佽�呮槸鍚﹀湪濂藉弸蹇界暐鍒楄〃涓�
+        is_friend_ignored = friend_ignore_service.is_friend_ignored(from_user)
+
+        if is_friend_ignored:
+            logger.info(f"蹇界暐濂藉弸鍙戦�佺殑娑堟伅: fromUser={from_user}, fromGroup={from_group}")
+            # 缁熻琚拷鐣ョ殑濂藉弸鍙戣█娆℃暟锛堢‘淇濊蹇界暐鐨勫ソ鍙嬫秷鎭篃绾冲叆缁熻锛�
+            group_stats_service.increment_user_message_count(from_group, from_user)
+            # 婵�娲绘垨寤堕暱璇ョ兢缁勭殑闈欓粯妯″紡
+            if silence_service.is_silence_active(from_group):
+                # 濡傛灉璇ョ兢缁勯潤榛樻ā寮忓凡婵�娲伙紝寤堕暱鏃堕棿
+                silence_service.extend_silence_mode(from_group)
+                logger.info(f"濂藉弸娑堟伅琚拷鐣ワ紝缇ょ粍闈欓粯妯″紡鏃堕棿宸插埛鏂�: fromUser={from_user}, fromGroup={from_group}")
+            else:
+                # 濡傛灉璇ョ兢缁勯潤榛樻ā寮忔湭婵�娲伙紝婵�娲婚潤榛樻ā寮�
+                silence_service.activate_silence_mode(from_group)
+                logger.info(f"濂藉弸娑堟伅琚拷鐣ワ紝缇ょ粍闈欓粯妯″紡宸叉縺娲�: fromUser={from_user}, fromGroup={from_group}")
+            return False
+
+        # # 缁熻姝e父澶勭悊鐨勫ソ鍙嬪彂瑷�娆℃暟
+        # group_stats_service.increment_user_message_count(from_group, from_user)
+
+        # 妫�鏌ヨ缇ょ粍鐨勯潤榛樻ā寮忔槸鍚︽縺娲伙紙鍦ㄥソ鍙嬪拷鐣ユ鏌ヤ箣鍚庯級
+        if silence_service.is_silence_active(from_group):
+            logger.info(f"缇ょ粍闈欓粯妯″紡婵�娲讳腑锛屽拷鐣ユ秷鎭�: fromGroup={from_group}")
             return False
 
         return True
@@ -214,12 +240,19 @@
                     logger.error(f"鑱旂郴浜轰俊鎭鐞嗗け璐�: from_group={from_group}")
                     return False
 
-                # 3.2 鑾峰彇鐢ㄦ埛鍦ㄥ綋鍓嶇兢缁勭殑conversation_id
+                # 3.2 鑾峰彇缇ょ粍涓彂瑷�娆℃暟鏈�澶氱殑鐢ㄦ埛鏄电О
+                most_active_nickname = group_stats_service.get_most_active_user_nickname(from_group)
+                logger.info(f"缇ょ粍鏈�娲昏穬鐢ㄦ埛鏄电О: group={from_group}, nickname={most_active_nickname}")
+
+                # 3.3 鑾峰彇鐢ㄦ埛鍦ㄥ綋鍓嶇兢缁勭殑conversation_id
                 conversation_id = redis_queue.get_conversation_id(from_user, from_group)
 
                 # 璋冪敤Dify鎺ュ彛鍙戦�佹秷鎭紙鏍规嵁閰嶇疆閫夋嫨妯″紡锛�
                 dify_response = dify_client.send_message(
-                    query=content, user=from_user, conversation_id=conversation_id
+                    query=content,
+                    user=from_user,
+                    conversation_id=conversation_id,
+                    nick_name=most_active_nickname
                 )
 
                 if not dify_response:
@@ -232,9 +265,9 @@
 
                 # 鏇存柊Redis涓殑conversation_id锛堝熀浜庣敤鎴�+缇ょ粍锛�
                 if new_conversation_id:
-                    redis_queue.set_conversation_id(from_user, new_conversation_id, from_group)
+                    redis_queue.set_conversation_id(from_user, new_conversation_id, from_group, 1800)
 
-                # 3.3 淇濆瓨瀵硅瘽璁板綍鍒版暟鎹簱
+                # 3.4 淇濆瓨瀵硅瘽璁板綍鍒版暟鎹簱
                 # 鎸夌敤鎴枫�佺兢缁勫拰灏忔椂鍒嗙粍瀵硅瘽璁板綍
                 current_time = datetime.now()
                 hour_key = current_time.strftime("%Y%m%d_%H")
diff --git a/app/services/silence_service.py b/app/services/silence_service.py
new file mode 100644
index 0000000..0161198
--- /dev/null
+++ b/app/services/silence_service.py
@@ -0,0 +1,253 @@
+"""
+闈欓粯妯″紡绠$悊鏈嶅姟
+"""
+
+import time
+from typing import Optional
+from loguru import logger
+
+from app.services.redis_queue import redis_queue
+from config import settings
+
+
+class SilenceService:
+    """闈欓粯妯″紡绠$悊鏈嶅姟"""
+
+    def __init__(self):
+        self.silence_key_prefix = "ecloud_silence_mode:"
+        self.silence_end_time_key_prefix = "ecloud_silence_end_time:"
+
+    def _get_group_silence_key(self, group_id: str) -> str:
+        """鑾峰彇缇ょ粍闈欓粯妯″紡閿�"""
+        return f"{self.silence_key_prefix}{group_id}"
+
+    def _get_group_silence_end_time_key(self, group_id: str) -> str:
+        """鑾峰彇缇ょ粍闈欓粯缁撴潫鏃堕棿閿�"""
+        return f"{self.silence_end_time_key_prefix}{group_id}"
+
+    def activate_silence_mode(self, group_id: str) -> bool:
+        """
+        婵�娲绘寚瀹氱兢缁勭殑闈欓粯妯″紡
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            婵�娲绘垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
+        """
+        try:
+            if not settings.silence_mode_enabled:
+                logger.debug("闈欓粯妯″紡鍔熻兘宸茬鐢�")
+                return False
+
+            # 璁$畻闈欓粯缁撴潫鏃堕棿锛堝綋鍓嶆椂闂� + 閰嶇疆鐨勫垎閽熸暟锛�
+            silence_duration_seconds = settings.silence_duration_minutes * 60
+            end_time = time.time() + silence_duration_seconds
+
+            # 鑾峰彇缇ょ粍涓撶敤鐨勯敭
+            group_silence_key = self._get_group_silence_key(group_id)
+            group_end_time_key = self._get_group_silence_end_time_key(group_id)
+
+            # 璁剧疆闈欓粯妯″紡鏍囧織鍜岀粨鏉熸椂闂�
+            redis_queue.redis_client.setex(
+                group_silence_key,
+                silence_duration_seconds,
+                "active"
+            )
+            redis_queue.redis_client.setex(
+                group_end_time_key,
+                silence_duration_seconds,
+                str(end_time)
+            )
+
+            logger.info(f"缇ょ粍闈欓粯妯″紡宸叉縺娲�: group_id={group_id}, 鎸佺画鏃堕棿: {settings.silence_duration_minutes} 鍒嗛挓")
+            return True
+
+        except Exception as e:
+            logger.error(f"婵�娲荤兢缁勯潤榛樻ā寮忓け璐�: group_id={group_id}, error={str(e)}")
+            return False
+
+    def extend_silence_mode(self, group_id: str) -> bool:
+        """
+        寤堕暱鎸囧畾缇ょ粍鐨勯潤榛樻ā寮忔椂闂达紙鍒锋柊闈欓粯鏃堕暱锛�
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            寤堕暱鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        try:
+            if not settings.silence_mode_enabled:
+                logger.debug("闈欓粯妯″紡鍔熻兘宸茬鐢�")
+                return False
+
+            # 閲嶆柊婵�娲婚潤榛樻ā寮忥紙鐩稿綋浜庡埛鏂版椂闀匡級
+            return self.activate_silence_mode(group_id)
+
+        except Exception as e:
+            logger.error(f"寤堕暱缇ょ粍闈欓粯妯″紡澶辫触: group_id={group_id}, error={str(e)}")
+            return False
+
+    def is_silence_active(self, group_id: str) -> bool:
+        """
+        妫�鏌ユ寚瀹氱兢缁勭殑闈欓粯妯″紡鏄惁婵�娲�
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            濡傛灉闈欓粯妯″紡婵�娲昏繑鍥濼rue锛屽惁鍒欒繑鍥濬alse
+        """
+        try:
+            if not settings.silence_mode_enabled:
+                return False
+
+            # 妫�鏌ョ兢缁勯潤榛樻ā寮忔爣蹇楁槸鍚﹀瓨鍦�
+            group_silence_key = self._get_group_silence_key(group_id)
+            is_active = redis_queue.redis_client.exists(group_silence_key)
+
+            if is_active:
+                logger.debug(f"缇ょ粍闈欓粯妯″紡褰撳墠澶勪簬婵�娲荤姸鎬�: group_id={group_id}")
+
+            return bool(is_active)
+
+        except Exception as e:
+            logger.error(f"妫�鏌ョ兢缁勯潤榛樻ā寮忕姸鎬佸け璐�: group_id={group_id}, error={str(e)}")
+            return False
+
+    def get_silence_remaining_time(self, group_id: str) -> Optional[int]:
+        """
+        鑾峰彇鎸囧畾缇ょ粍鐨勯潤榛樻ā寮忓墿浣欐椂闂达紙绉掞級
+
+        Args:
+            group_id: 缇ょ粍ID
+
+        Returns:
+            鍓╀綑鏃堕棿锛堢锛夛紝濡傛灉闈欓粯妯″紡鏈縺娲昏繑鍥濶one
+        """
+        try:
+            if not self.is_silence_active(group_id):
+                return None
+
+            # 鑾峰彇缇ょ粍闈欓粯缁撴潫鏃堕棿
+            group_end_time_key = self._get_group_silence_end_time_key(group_id)
+            end_time_str = redis_queue.redis_client.get(group_end_time_key)
+            if not end_time_str:
+                return None
+
+            end_time = float(end_time_str)
+            current_time = time.time()
+            remaining_time = int(end_time - current_time)
+
+            return max(0, remaining_time)
+
+        except Exception as e:
+            logger.error(f"鑾峰彇缇ょ粍闈欓粯妯″紡鍓╀綑鏃堕棿澶辫触: group_id={group_id}, error={str(e)}")
+            return None
+
+    def deactivate_silence_mode(self, group_id: str = None) -> bool:
+        """
+        鎵嬪姩鍋滅敤闈欓粯妯″紡
+
+        Args:
+            group_id: 缇ょ粍ID锛屽鏋滀负None鍒欏仠鐢ㄦ墍鏈夌兢缁勭殑闈欓粯妯″紡
+
+        Returns:
+            鍋滅敤鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        try:
+            if group_id:
+                # 鍋滅敤鎸囧畾缇ょ粍鐨勯潤榛樻ā寮�
+                group_silence_key = self._get_group_silence_key(group_id)
+                group_end_time_key = self._get_group_silence_end_time_key(group_id)
+
+                redis_queue.redis_client.delete(group_silence_key)
+                redis_queue.redis_client.delete(group_end_time_key)
+
+                logger.info(f"缇ょ粍闈欓粯妯″紡宸叉墜鍔ㄥ仠鐢�: group_id={group_id}")
+            else:
+                # 鍋滅敤鎵�鏈夌兢缁勭殑闈欓粯妯″紡
+                silence_keys = redis_queue.redis_client.keys(f"{self.silence_key_prefix}*")
+                end_time_keys = redis_queue.redis_client.keys(f"{self.silence_end_time_key_prefix}*")
+
+                all_keys = silence_keys + end_time_keys
+                if all_keys:
+                    redis_queue.redis_client.delete(*all_keys)
+
+                logger.info("鎵�鏈夌兢缁勭殑闈欓粯妯″紡宸叉墜鍔ㄥ仠鐢�")
+
+            return True
+
+        except Exception as e:
+            logger.error(f"鍋滅敤闈欓粯妯″紡澶辫触: group_id={group_id}, error={str(e)}")
+            return False
+
+    def get_silence_status(self, group_id: str = None) -> dict:
+        """
+        鑾峰彇闈欓粯妯″紡璇︾粏鐘舵�佷俊鎭�
+
+        Args:
+            group_id: 缇ょ粍ID锛屽鏋滀负None鍒欒繑鍥炲叏灞�鐘舵�佹瑙�
+
+        Returns:
+            鍖呭惈闈欓粯妯″紡鐘舵�佷俊鎭殑瀛楀吀
+        """
+        try:
+            if group_id:
+                # 鑾峰彇鎸囧畾缇ょ粍鐨勭姸鎬�
+                status = {
+                    "enabled": settings.silence_mode_enabled,
+                    "group_id": group_id,
+                    "active": False,
+                    "remaining_seconds": None,
+                    "remaining_minutes": None,
+                    "duration_minutes": settings.silence_duration_minutes
+                }
+
+                if settings.silence_mode_enabled:
+                    status["active"] = self.is_silence_active(group_id)
+                    if status["active"]:
+                        remaining_seconds = self.get_silence_remaining_time(group_id)
+                        if remaining_seconds is not None:
+                            status["remaining_seconds"] = remaining_seconds
+                            status["remaining_minutes"] = round(remaining_seconds / 60, 1)
+            else:
+                # 鑾峰彇鍏ㄥ眬鐘舵�佹瑙�
+                active_groups = []
+                if settings.silence_mode_enabled:
+                    # 鏌ユ壘鎵�鏈夋縺娲荤殑缇ょ粍
+                    silence_keys = redis_queue.redis_client.keys(f"{self.silence_key_prefix}*")
+                    for key in silence_keys:
+                        group_id_from_key = key.replace(self.silence_key_prefix, "")
+                        remaining_time = self.get_silence_remaining_time(group_id_from_key)
+                        if remaining_time and remaining_time > 0:
+                            active_groups.append({
+                                "group_id": group_id_from_key,
+                                "remaining_seconds": remaining_time,
+                                "remaining_minutes": round(remaining_time / 60, 1)
+                            })
+
+                status = {
+                    "enabled": settings.silence_mode_enabled,
+                    "active_groups_count": len(active_groups),
+                    "active_groups": active_groups,
+                    "duration_minutes": settings.silence_duration_minutes
+                }
+
+            return status
+
+        except Exception as e:
+            logger.error(f"鑾峰彇闈欓粯妯″紡鐘舵�佸け璐�: group_id={group_id}, error={str(e)}")
+            return {
+                "enabled": False,
+                "active": False,
+                "remaining_seconds": None,
+                "remaining_minutes": None,
+                "duration_minutes": 0,
+                "error": str(e)
+            }
+
+
+# 鍏ㄥ眬闈欓粯鏈嶅姟瀹炰緥
+silence_service = SilenceService()
diff --git a/app/services/sms_service.py b/app/services/sms_service.py
new file mode 100644
index 0000000..a592039
--- /dev/null
+++ b/app/services/sms_service.py
@@ -0,0 +1,143 @@
+"""
+鐭俊鍙戦�佹湇鍔�
+"""
+
+import hashlib
+import time
+import requests
+from typing import List, Optional
+from loguru import logger
+from config import settings
+
+
+class SmsService:
+    """鐭俊鍙戦�佹湇鍔�"""
+
+    def __init__(self):
+        self.api_url = settings.sms_api_url
+        self.username = settings.sms_username
+        self.password = settings.sms_password
+        self.session = requests.Session()
+        self.session.headers.update({
+            "Accept": "application/json",
+            "Content-Type": "application/json;charset=utf-8"
+        })
+
+    def _generate_sign(self, timestamp: int) -> str:
+        """
+        鐢熸垚绛惧悕
+        璁$畻瑙勫垯锛歁D5(userName+timestamp+MD5(password))
+
+        Args:
+            timestamp: 鏃堕棿鎴筹紙姣锛�
+
+        Returns:
+            绛惧悕瀛楃涓�
+        """
+        # 璁$畻瀵嗙爜鐨凪D5
+        password_md5 = hashlib.md5(self.password.encode('utf-8')).hexdigest()
+        
+        # 缁勫悎瀛楃涓诧細userName+timestamp+MD5(password)
+        combined_str = f"{self.username}{timestamp}{password_md5}"
+        
+        # 璁$畻鏈�缁堢鍚�
+        sign = hashlib.md5(combined_str.encode('utf-8')).hexdigest()
+        
+        return sign
+
+    def send_sms(self, phone_list: List[str], content: str) -> bool:
+        """
+        鍙戦�佺煭淇�
+
+        Args:
+            phone_list: 鎵嬫満鍙风爜鍒楄〃
+            content: 鐭俊鍐呭
+
+        Returns:
+            鍙戦�佹垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
+        """
+        if not settings.sms_enabled:
+            logger.info("鐭俊鍙戦�佸姛鑳藉凡绂佺敤")
+            return True
+
+        if not phone_list:
+            logger.warning("鎵嬫満鍙风爜鍒楄〃涓虹┖")
+            return False
+
+        try:
+            # 鐢熸垚鏃堕棿鎴筹紙姣锛�
+            timestamp = int(time.time() * 1000)
+            
+            # 鐢熸垚绛惧悕
+            sign = self._generate_sign(timestamp)
+
+            # 鏋勫缓璇锋眰鍙傛暟
+            payload = {
+                "userName": self.username,
+                "content": content,
+                "phoneList": phone_list,
+                "timestamp": timestamp,
+                "sign": sign
+            }
+
+            logger.info(f"鍙戦�佺煭淇�: phones={phone_list}, content_length={len(content)}")
+
+            # 鍙戦�佽姹�
+            response = self.session.post(self.api_url, json=payload, timeout=30)
+            response.raise_for_status()
+
+            result = response.json()
+
+            if result.get("code") == 0:
+                msg_id = result.get("msgId")
+                sms_count = result.get("smsCount")
+                logger.info(f"鐭俊鍙戦�佹垚鍔�: msgId={msg_id}, smsCount={sms_count}, phones={phone_list}")
+                return True
+            else:
+                logger.error(
+                    f"鐭俊鍙戦�佸け璐�: code={result.get('code')}, message={result.get('message')}, phones={phone_list}"
+                )
+                return False
+
+        except requests.exceptions.RequestException as e:
+            logger.error(f"鐭俊鍙戦�佺綉缁滈敊璇�: phones={phone_list}, error={str(e)}")
+            return False
+        except Exception as e:
+            logger.error(f"鐭俊鍙戦�佸紓甯�: phones={phone_list}, error={str(e)}")
+            return False
+
+    def send_notification(self, content: str) -> bool:
+        """
+        鍙戦�侀�氱煡鐭俊鍒伴厤缃殑鎵嬫満鍙风爜鍒楄〃
+
+        Args:
+            content: 鐭俊鍐呭
+
+        Returns:
+            鍙戦�佹垚鍔熻繑鍥濼rue锛屽け璐ヨ繑鍥濬alse
+        """
+        return self.send_sms(settings.sms_phone_numbers, content)
+
+    def test_connection(self) -> bool:
+        """
+        娴嬭瘯鐭俊鏈嶅姟杩炴帴锛堝彂閫佹祴璇曠煭淇★級
+
+        Returns:
+            杩炴帴鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        if not settings.sms_enabled:
+            logger.info("鐭俊鍙戦�佸姛鑳藉凡绂佺敤")
+            return True
+
+        # 鍙戦�佹祴璇曠煭淇″埌绗竴涓彿鐮�
+        if settings.sms_phone_numbers:
+            test_phone = [settings.sms_phone_numbers[0]]
+            test_content = "銆愭祴璇曘�戠煭淇℃湇鍔¤繛鎺ユ祴璇�"
+            return self.send_sms(test_phone, test_content)
+        else:
+            logger.warning("娌℃湁閰嶇疆鐭俊鎺ユ敹鍙风爜")
+            return False
+
+
+# 鍏ㄥ眬鐭俊鏈嶅姟瀹炰緥
+sms_service = SmsService()
diff --git a/app/workers/online_status_worker.py b/app/workers/online_status_worker.py
new file mode 100644
index 0000000..c68d325
--- /dev/null
+++ b/app/workers/online_status_worker.py
@@ -0,0 +1,188 @@
+"""
+鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋�
+"""
+
+import time
+import threading
+from datetime import datetime
+from loguru import logger
+from config import settings
+from app.services.ecloud_client import ecloud_client
+from app.services.email_service import email_service
+from app.services.sms_service import sms_service
+
+
+class OnlineStatusWorker:
+    """鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋�"""
+
+    def __init__(self):
+        self.running = False
+        self.worker_thread = None
+        self.last_notification_time = None
+        self.notification_cooldown = 30 * 60  # 30鍒嗛挓鍐峰嵈鏃堕棿锛岄伩鍏嶉绻佸彂閫侀�氱煡
+
+    def start(self):
+        """鍚姩鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋�"""
+        if not settings.online_status_enabled:
+            logger.info("鍦ㄧ嚎鐘舵�佹娴嬪姛鑳藉凡绂佺敤")
+            return
+
+        self.running = True
+        self.worker_thread = threading.Thread(target=self._monitor_online_status, daemon=True)
+        self.worker_thread.start()
+        logger.info("鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋嬪凡鍚姩")
+
+    def stop(self):
+        """鍋滄鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋�"""
+        self.running = False
+        if self.worker_thread and self.worker_thread.is_alive():
+            self.worker_thread.join(timeout=10)
+        logger.info("鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋嬪凡鍋滄")
+
+    def _monitor_online_status(self):
+        """鐩戞帶鍦ㄧ嚎鐘舵�佺殑涓诲惊鐜�"""
+        logger.info(f"寮�濮嬬洃鎺у湪绾跨姸鎬侊紝妫�娴嬮棿闅�: {settings.online_status_check_interval}鍒嗛挓")
+        
+        while self.running:
+            try:
+                # 妫�娴嬪湪绾跨姸鎬�
+                self._check_online_status()
+                
+                # 绛夊緟涓嬫妫�娴�
+                sleep_seconds = settings.online_status_check_interval * 60
+                for _ in range(sleep_seconds):
+                    if not self.running:
+                        break
+                    time.sleep(1)
+                    
+            except Exception as e:
+                logger.error(f"鍦ㄧ嚎鐘舵�佺洃鎺у紓甯�: {str(e)}")
+                time.sleep(60)  # 寮傚父鏃剁瓑寰�1鍒嗛挓鍐嶇户缁�
+
+    def _check_online_status(self):
+        """妫�娴嬪湪绾跨姸鎬佸苟鍙戦�侀�氱煡"""
+        try:
+            logger.info("寮�濮嬫娴嬪井淇″湪绾跨姸鎬�")
+            
+            # 鏌ヨ鍦ㄧ嚎寰俊鍒楄〃
+            online_list = ecloud_client.query_online_wechat_list()
+            
+            if online_list is None:
+                logger.error("鏌ヨ鍦ㄧ嚎寰俊鍒楄〃澶辫触锛岃烦杩囨湰娆℃娴�")
+                return
+            
+            online_count = len(online_list)
+            logger.info(f"褰撳墠鍦ㄧ嚎寰俊鏁伴噺: {online_count}")
+            
+            # 濡傛灉鏈夊湪绾垮井淇★紝鏇存柊w_id骞堕噸缃�氱煡鏃堕棿
+            if online_count > 0:
+                # 鑾峰彇绗竴涓湪绾垮井淇$殑w_id骞舵洿鏂伴厤缃�
+                if online_list and len(online_list) > 0:
+                    current_w_id = online_list[0].get("wId")
+                    if current_w_id:
+                        self._update_w_id_if_needed(current_w_id)
+
+                if self.last_notification_time:
+                    logger.info("妫�娴嬪埌鍦ㄧ嚎寰俊锛岄噸缃�氱煡鐘舵��")
+                    self.last_notification_time = None
+                return
+            
+            # 娌℃湁鍦ㄧ嚎寰俊锛屾鏌ユ槸鍚﹂渶瑕佸彂閫侀�氱煡
+            current_time = time.time()
+            
+            # 妫�鏌ュ喎鍗存椂闂�
+            if (self.last_notification_time and 
+                current_time - self.last_notification_time < self.notification_cooldown):
+                remaining_time = self.notification_cooldown - (current_time - self.last_notification_time)
+                logger.info(f"閫氱煡鍐峰嵈涓紝鍓╀綑鏃堕棿: {remaining_time/60:.1f}鍒嗛挓")
+                return
+            
+            # 鍙戦�佹帀绾块�氱煡
+            logger.warning("妫�娴嬪埌寰俊鍏ㄩ儴鎺夌嚎锛屽彂閫侀�氱煡")
+            self._send_offline_notification()
+            self.last_notification_time = current_time
+            
+        except Exception as e:
+            logger.error(f"妫�娴嬪湪绾跨姸鎬佸紓甯�: {str(e)}")
+
+    def _update_w_id_if_needed(self, current_w_id: str):
+        """
+        濡傛灉闇�瑕侊紝鏇存柊w_id閰嶇疆
+
+        Args:
+            current_w_id: 褰撳墠妫�娴嬪埌鐨剋_id
+        """
+        try:
+            # 鑾峰彇閰嶇疆涓殑褰撳墠w_id
+            config_w_id = settings.get_current_w_id()
+
+            # 濡傛灉w_id鍙戠敓鍙樺寲锛屽垯鏇存柊閰嶇疆
+            if current_w_id != config_w_id:
+                logger.info(f"妫�娴嬪埌w_id鍙樺寲: {config_w_id} -> {current_w_id}")
+                success = settings.update_ecloud_w_id(current_w_id)
+                if success:
+                    logger.info("w_id鏇存柊鎴愬姛")
+                else:
+                    logger.error("w_id鏇存柊澶辫触")
+
+        except Exception as e:
+            logger.error(f"鏇存柊w_id寮傚父: {str(e)}")
+
+    def _send_offline_notification(self):
+        """鍙戦�佹帀绾块�氱煡"""
+        try:
+            # 鍑嗗閫氱煡鍐呭
+            subject = "寰俊鎺夌嚎鎻愰啋"
+            content = settings.online_status_notification_message
+            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+            
+            # 娣诲姞鏃堕棿鎴冲埌鍐呭
+            full_content = f"{content}\n\n妫�娴嬫椂闂�: {timestamp}"
+            
+            # 鍙戦�侀偖浠堕�氱煡
+            email_success = False
+            if settings.email_enabled:
+                try:
+                    email_success = email_service.send_notification(subject, full_content)
+                    if email_success:
+                        logger.info("鎺夌嚎閫氱煡閭欢鍙戦�佹垚鍔�")
+                    else:
+                        logger.error("鎺夌嚎閫氱煡閭欢鍙戦�佸け璐�")
+                except Exception as e:
+                    logger.error(f"鍙戦�佹帀绾块�氱煡閭欢寮傚父: {str(e)}")
+            
+            # 鍙戦�佺煭淇¢�氱煡
+            sms_success = False
+            if settings.sms_enabled:
+                try:
+                    # 鐭俊鍐呭涓嶅寘鍚椂闂存埑锛岄伩鍏嶈繃闀�
+                    sms_success = sms_service.send_notification(content)
+                    if sms_success:
+                        logger.info("鎺夌嚎閫氱煡鐭俊鍙戦�佹垚鍔�")
+                    else:
+                        logger.error("鎺夌嚎閫氱煡鐭俊鍙戦�佸け璐�")
+                except Exception as e:
+                    logger.error(f"鍙戦�佹帀绾块�氱煡鐭俊寮傚父: {str(e)}")
+            
+            # 璁板綍閫氱煡缁撴灉
+            if email_success or sms_success:
+                logger.info("鎺夌嚎閫氱煡鍙戦�佸畬鎴�")
+            else:
+                logger.error("鎺夌嚎閫氱煡鍙戦�佸叏閮ㄥけ璐�")
+                
+        except Exception as e:
+            logger.error(f"鍙戦�佹帀绾块�氱煡寮傚父: {str(e)}")
+
+    def get_status(self) -> dict:
+        """鑾峰彇宸ヤ綔杩涚▼鐘舵��"""
+        return {
+            "running": self.running,
+            "enabled": settings.online_status_enabled,
+            "check_interval_minutes": settings.online_status_check_interval,
+            "last_notification_time": self.last_notification_time,
+            "notification_cooldown_minutes": self.notification_cooldown / 60
+        }
+
+
+# 鍏ㄥ眬鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋嬪疄渚�
+online_status_worker = OnlineStatusWorker()
diff --git a/config.example.json b/config.example.json
new file mode 100644
index 0000000..f64b2f5
--- /dev/null
+++ b/config.example.json
@@ -0,0 +1,65 @@
+{
+  "database": {
+    "url": "mysql+pymysql://username:password@host:port/database"
+  },
+  "redis": {
+    "url": "redis://localhost:6379/0"
+  },
+  "ecloud": {
+    "base_url": "http://your-ecloud-server:port",
+    "authorization": "your-authorization-token",
+    "w_id": "your-wechat-instance-id"
+  },
+  "dify": {
+    "base_url": "https://api.dify.ai/v1",
+    "api_key": "your-dify-api-key",
+    "streaming_enabled": true,
+    "streaming_timeout": 1200
+  },
+  "server": {
+    "host": "0.0.0.0",
+    "port": 7979,
+    "debug": false
+  },
+  "logging": {
+    "level": "INFO",
+    "file": "logs/app.log"
+  },
+  "message_processing": {
+    "max_retry_count": 3,
+    "retry_delay": 5,
+    "queue_timeout": 300
+  },
+  "customer_service": {
+    "names": ["瀹㈡湇1", "瀹㈡湇2"]
+  },
+  "friend_ignore": {
+    "enabled": true,
+    "whitelist": []
+  },
+  "silence_mode": {
+    "enabled": true,
+    "duration_minutes": 10
+  },
+  "online_status_monitor": {
+    "enabled": true,
+    "check_interval_minutes": 5,
+    "notification_message": "寰俊鎺夌嚎鎻愰啋锛氬綋鍓嶆病鏈夊湪绾跨殑寰俊璐﹀彿锛岃鍙婃椂妫�鏌ワ紒"
+  },
+  "email_notification": {
+    "enabled": true,
+    "smtp_server": "smtp.qq.com",
+    "smtp_port": 587,
+    "smtp_username": "your_email@qq.com",
+    "smtp_password": "your_qq_app_password",
+    "from_email": "your_email@qq.com",
+    "to_emails": ["admin@example.com", "backup@example.com"]
+  },
+  "sms_notification": {
+    "enabled": true,
+    "api_url": "https://smsapi.izjun.com:8443/sms/api/sendMessageMass",
+    "username": "your_sms_username",
+    "password": "your_sms_password",
+    "phone_numbers": ["13800138000", "13900139000"]
+  }
+}
diff --git a/config.py b/config.py
index c9792f3..e0b1455 100644
--- a/config.py
+++ b/config.py
@@ -4,6 +4,8 @@
 
 import os
 import json
+import threading
+from loguru import logger
 
 
 class Settings:
@@ -12,6 +14,7 @@
     def __init__(self, config_file: str = "config.json"):
         """鍒濆鍖栭厤缃紝浠嶫SON鏂囦欢鍔犺浇"""
         self.config_file = config_file
+        self._lock = threading.Lock()  # 鐢ㄤ簬绾跨▼瀹夊叏鐨勯厤缃洿鏂�
         self._load_config()
 
     def _load_config(self):
@@ -67,12 +70,118 @@
         # 瀹㈡湇閰嶇疆
         customer_service_config = config_data["customer_service"]
         self.customer_service_names = customer_service_config["names"]
+        self.customer_service_default_name = customer_service_config.get("default_name", "鏅鸿兘瀹㈡湇")
 
         # 濂藉弸蹇界暐閰嶇疆
         friend_ignore_config = config_data["friend_ignore"]
         self.friend_ignore_enabled = friend_ignore_config["enabled"]
         self.friend_ignore_whitelist = friend_ignore_config["whitelist"]
 
+        # 闈欓粯妯″紡閰嶇疆
+        silence_mode_config = config_data["silence_mode"]
+        self.silence_mode_enabled = silence_mode_config["enabled"]
+        self.silence_duration_minutes = silence_mode_config["duration_minutes"]
+
+        # 鍦ㄧ嚎鐘舵�佺洃鎺ч厤缃�
+        online_status_config = config_data["online_status_monitor"]
+        self.online_status_enabled = online_status_config["enabled"]
+        self.online_status_check_interval = online_status_config["check_interval_minutes"]
+        self.online_status_notification_message = online_status_config["notification_message"]
+
+        # 閭欢閫氱煡閰嶇疆
+        email_config = config_data["email_notification"]
+        self.email_enabled = email_config["enabled"]
+        self.email_smtp_server = email_config["smtp_server"]
+        self.email_smtp_port = email_config["smtp_port"]
+        self.email_smtp_username = email_config["smtp_username"]
+        self.email_smtp_password = email_config["smtp_password"]
+        self.email_from_email = email_config["from_email"]
+        self.email_to_emails = email_config["to_emails"]
+
+        # 鐭俊閫氱煡閰嶇疆
+        sms_config = config_data["sms_notification"]
+        self.sms_enabled = sms_config["enabled"]
+        self.sms_api_url = sms_config["api_url"]
+        self.sms_username = sms_config["username"]
+        self.sms_password = sms_config["password"]
+        self.sms_phone_numbers = sms_config["phone_numbers"]
+
+        # 鍏抽敭璇嶈繃婊ら厤缃�
+        keyword_filter_config = config_data.get("keyword_filter", {})
+        self.keyword_filter_enabled = keyword_filter_config.get("enabled", False)
+        self.keyword_filter_keywords = keyword_filter_config.get("keywords", [])
+
+    def update_ecloud_w_id(self, new_w_id: str) -> bool:
+        """
+        鍔ㄦ�佹洿鏂癊浜戠瀹剁殑w_id閰嶇疆
+
+        Args:
+            new_w_id: 鏂扮殑w_id鍊�
+
+        Returns:
+            鏇存柊鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        if not new_w_id or new_w_id == self.ecloud_w_id:
+            return True
+
+        with self._lock:
+            try:
+                # 鏇存柊鍐呭瓨涓殑閰嶇疆
+                old_w_id = self.ecloud_w_id
+                self.ecloud_w_id = new_w_id
+
+                # 鏇存柊閰嶇疆鏂囦欢
+                if self._update_config_file_w_id(new_w_id):
+                    logger.info(f"鎴愬姛鏇存柊w_id: {old_w_id} -> {new_w_id}")
+                    return True
+                else:
+                    # 濡傛灉鏂囦欢鏇存柊澶辫触锛屽洖婊氬唴瀛橀厤缃�
+                    self.ecloud_w_id = old_w_id
+                    logger.error(f"鏇存柊閰嶇疆鏂囦欢澶辫触锛屽洖婊歸_id: {new_w_id} -> {old_w_id}")
+                    return False
+
+            except Exception as e:
+                logger.error(f"鏇存柊w_id寮傚父: {str(e)}")
+                return False
+
+    def _update_config_file_w_id(self, new_w_id: str) -> bool:
+        """
+        鏇存柊閰嶇疆鏂囦欢涓殑w_id
+
+        Args:
+            new_w_id: 鏂扮殑w_id鍊�
+
+        Returns:
+            鏇存柊鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse
+        """
+        try:
+            # 璇诲彇褰撳墠閰嶇疆鏂囦欢
+            with open(self.config_file, 'r', encoding='utf-8') as f:
+                config_data = json.load(f)
+
+            # 鏇存柊w_id
+            config_data["ecloud"]["w_id"] = new_w_id
+
+            # 鍐欏洖閰嶇疆鏂囦欢
+            with open(self.config_file, 'w', encoding='utf-8') as f:
+                json.dump(config_data, f, indent=2, ensure_ascii=False)
+
+            return True
+
+        except Exception as e:
+            logger.error(f"鏇存柊閰嶇疆鏂囦欢w_id澶辫触: {str(e)}")
+            return False
+
+    def get_current_w_id(self) -> str:
+        """
+        鑾峰彇褰撳墠鐨剋_id
+
+        Returns:
+            褰撳墠鐨剋_id鍊�
+        """
+        with self._lock:
+            return self.ecloud_w_id
+
 
 # 鍏ㄥ眬閰嶇疆瀹炰緥
 settings = Settings()
diff --git a/example_usage.py b/example_usage.py
new file mode 100644
index 0000000..e6373a1
--- /dev/null
+++ b/example_usage.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+"""
+鍦ㄧ嚎鐘舵�佺洃鎺у姛鑳戒娇鐢ㄧず渚�
+"""
+
+import asyncio
+import time
+from config import settings
+from app.services.ecloud_client import ecloud_client
+from app.services.email_service import email_service
+from app.services.sms_service import sms_service
+from app.workers.online_status_worker import online_status_worker
+
+
+async def test_online_status_monitoring():
+    """娴嬭瘯鍦ㄧ嚎鐘舵�佺洃鎺у姛鑳�"""
+    print("=== 鍦ㄧ嚎鐘舵�佺洃鎺у姛鑳芥祴璇� ===\n")
+    
+    # 1. 娴嬭瘯鏌ヨ鍦ㄧ嚎寰俊鍒楄〃
+    print("1. 娴嬭瘯鏌ヨ鍦ㄧ嚎寰俊鍒楄〃...")
+    online_list = ecloud_client.query_online_wechat_list()
+    if online_list is not None:
+        print(f"   鏌ヨ鎴愬姛锛屽湪绾垮井淇℃暟閲�: {len(online_list)}")
+        for i, wechat in enumerate(online_list):
+            print(f"   寰俊{i+1}: wcId={wechat.get('wcId')}, wId={wechat.get('wId')}")
+    else:
+        print("   鏌ヨ澶辫触")
+    print()
+    
+    # 2. 娴嬭瘯閭欢鏈嶅姟
+    print("2. 娴嬭瘯閭欢鏈嶅姟...")
+    if settings.email_enabled:
+        # 娴嬭瘯杩炴帴
+        email_connected = email_service.test_connection()
+        print(f"   閭欢鏈嶅姟杩炴帴: {'鎴愬姛' if email_connected else '澶辫触'}")
+        
+        if email_connected:
+            # 鍙戦�佹祴璇曢偖浠�
+            test_subject = "鍦ㄧ嚎鐘舵�佺洃鎺ф祴璇曢偖浠�"
+            test_content = f"杩欐槸涓�灏佹祴璇曢偖浠讹紝鍙戦�佹椂闂�: {time.strftime('%Y-%m-%d %H:%M:%S')}"
+            email_sent = email_service.send_notification(test_subject, test_content)
+            print(f"   娴嬭瘯閭欢鍙戦��: {'鎴愬姛' if email_sent else '澶辫触'}")
+    else:
+        print("   閭欢鏈嶅姟宸茬鐢�")
+    print()
+    
+    # 3. 娴嬭瘯鐭俊鏈嶅姟
+    print("3. 娴嬭瘯鐭俊鏈嶅姟...")
+    if settings.sms_enabled:
+        # 鍙戦�佹祴璇曠煭淇�
+        test_content = f"銆愭祴璇曘�戝湪绾跨姸鎬佺洃鎺ф祴璇曠煭淇★紝鏃堕棿: {time.strftime('%H:%M:%S')}"
+        sms_sent = sms_service.send_notification(test_content)
+        print(f"   娴嬭瘯鐭俊鍙戦��: {'鎴愬姛' if sms_sent else '澶辫触'}")
+    else:
+        print("   鐭俊鏈嶅姟宸茬鐢�")
+    print()
+    
+    # 4. 娴嬭瘯w_id鍔ㄦ�佹洿鏂�
+    print("4. 娴嬭瘯w_id鍔ㄦ�佹洿鏂�...")
+    current_w_id = settings.get_current_w_id()
+    print(f"   褰撳墠閰嶇疆鐨剋_id: {current_w_id}")
+    
+    if online_list and len(online_list) > 0:
+        online_w_id = online_list[0].get("wId")
+        if online_w_id != current_w_id:
+            print(f"   妫�娴嬪埌w_id鍙樺寲: {current_w_id} -> {online_w_id}")
+            success = settings.update_ecloud_w_id(online_w_id)
+            print(f"   w_id鏇存柊: {'鎴愬姛' if success else '澶辫触'}")
+        else:
+            print("   w_id鏃犲彉鍖栵紝鏃犻渶鏇存柊")
+    else:
+        print("   鏃犲湪绾垮井淇★紝鏃犳硶娴嬭瘯w_id鏇存柊")
+    print()
+    
+    # 5. 鏄剧ず宸ヤ綔杩涚▼鐘舵��
+    print("5. 鍦ㄧ嚎鐘舵�佺洃鎺у伐浣滆繘绋嬬姸鎬�...")
+    status = online_status_worker.get_status()
+    print(f"   杩愯鐘舵��: {'杩愯涓�' if status['running'] else '宸插仠姝�'}")
+    print(f"   鍔熻兘鍚敤: {'鏄�' if status['enabled'] else '鍚�'}")
+    print(f"   妫�娴嬮棿闅�: {status['check_interval_minutes']}鍒嗛挓")
+    print(f"   閫氱煡鍐峰嵈: {status['notification_cooldown_minutes']}鍒嗛挓")
+    if status['last_notification_time']:
+        last_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(status['last_notification_time']))
+        print(f"   涓婃閫氱煡: {last_time}")
+    else:
+        print("   涓婃閫氱煡: 鏃�")
+    print()
+    
+    print("=== 娴嬭瘯瀹屾垚 ===")
+
+
+def show_configuration():
+    """鏄剧ず褰撳墠閰嶇疆"""
+    print("=== 褰撳墠閰嶇疆淇℃伅 ===\n")
+    
+    print("鍦ㄧ嚎鐘舵�佺洃鎺ч厤缃�:")
+    print(f"  鍚敤鐘舵��: {'鏄�' if settings.online_status_enabled else '鍚�'}")
+    print(f"  妫�娴嬮棿闅�: {settings.online_status_check_interval}鍒嗛挓")
+    print(f"  閫氱煡娑堟伅: {settings.online_status_notification_message}")
+    print()
+    
+    print("閭欢閫氱煡閰嶇疆:")
+    print(f"  鍚敤鐘舵��: {'鏄�' if settings.email_enabled else '鍚�'}")
+    if settings.email_enabled:
+        print(f"  SMTP鏈嶅姟鍣�: {settings.email_smtp_server}:{settings.email_smtp_port}")
+        print(f"  鍙戜欢浜�: {settings.email_from_email}")
+        print(f"  鏀朵欢浜�: {', '.join(settings.email_to_emails)}")
+    print()
+    
+    print("鐭俊閫氱煡閰嶇疆:")
+    print(f"  鍚敤鐘舵��: {'鏄�' if settings.sms_enabled else '鍚�'}")
+    if settings.sms_enabled:
+        print(f"  API鍦板潃: {settings.sms_api_url}")
+        print(f"  鐢ㄦ埛鍚�: {settings.sms_username}")
+        print(f"  鎺ユ敹鍙风爜: {', '.join(settings.sms_phone_numbers)}")
+    print()
+    
+    print("E浜戠瀹堕厤缃�:")
+    print(f"  API鍦板潃: {settings.ecloud_base_url}")
+    print(f"  褰撳墠w_id: {settings.ecloud_w_id}")
+    print()
+
+
+if __name__ == "__main__":
+    print("鍦ㄧ嚎鐘舵�佺洃鎺у姛鑳戒娇鐢ㄧず渚媆n")
+    
+    # 鏄剧ず閰嶇疆淇℃伅
+    show_configuration()
+    
+    # 杩愯娴嬭瘯
+    asyncio.run(test_online_status_monitoring())
+    
+    print("\n娉ㄦ剰浜嬮」:")
+    print("1. 璇风‘淇濆凡姝g‘閰嶇疆config.json涓殑鐩稿叧鍙傛暟")
+    print("2. 閭欢鍜岀煭淇℃祴璇曚細瀹為檯鍙戦�佹秷鎭紝璇疯皑鎱庝娇鐢�")
+    print("3. 寤鸿鍦ㄧ敓浜х幆澧冧腑绂佺敤娴嬭瘯鍔熻兘")
diff --git a/logs/app.log b/logs/app.log
index d8da35c..78fc2ba 100644
--- a/logs/app.log
+++ b/logs/app.log
@@ -2,3 +2,18 @@
 2025-07-28 10:41:14 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
 2025-07-28 10:42:46 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
 2025-07-28 11:10:52 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 11:23:47 | INFO | __main__:<module>:124 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 11:41:55 | INFO | __main__:<module>:124 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 11:44:17 | INFO | __main__:<module>:124 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 14:08:32 | INFO | __main__:<module>:124 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 14:31:14 | INFO | __main__:<module>:124 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 16:07:05 | INFO | __main__:<module>:139 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 16:23:36 | INFO | __main__:<module>:152 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 16:39:45 | INFO | __main__:<module>:159 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 17:17:10 | INFO | __main__:<module>:161 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 17:18:18 | INFO | __main__:<module>:161 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 17:29:25 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 17:29:50 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 17:34:00 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 17:44:45 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
+2025-07-28 18:12:07 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟
diff --git a/test_group_stats.py b/test_group_stats.py
new file mode 100644
index 0000000..e4aa7e0
--- /dev/null
+++ b/test_group_stats.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+"""
+缇ょ粍缁熻鍔熻兘娴嬭瘯鑴氭湰
+"""
+
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from app.services.group_stats_service import group_stats_service
+from config import settings
+
+
+def test_group_stats():
+    """娴嬭瘯缇ょ粍缁熻鍔熻兘"""
+    print("=== 缇ょ粍缁熻鍔熻兘娴嬭瘯 ===\n")
+    
+    # 娴嬭瘯缇ょ粍ID
+    test_group_id = "test_group_123"
+    test_users = ["user1", "user2", "user3"]
+    
+    print(f"1. 娴嬭瘯缇ょ粍: {test_group_id}")
+    print(f"2. 娴嬭瘯鐢ㄦ埛: {test_users}")
+    print(f"3. 榛樿瀹㈡湇鍚嶇О: {settings.customer_service_default_name}")
+    print()
+    
+    # 娓呯┖娴嬭瘯鏁版嵁
+    print("4. 娓呯┖娴嬭瘯鏁版嵁...")
+    group_stats_service.clear_group_stats(test_group_id)
+    print("   娓呯┖瀹屾垚")
+    print()
+    
+    # 娴嬭瘯鍒濆鐘舵��
+    print("5. 娴嬭瘯鍒濆鐘舵��...")
+    initial_stats = group_stats_service.get_group_message_stats(test_group_id)
+    initial_nickname = group_stats_service.get_most_active_user_nickname(test_group_id)
+    print(f"   鍒濆缁熻: {initial_stats}")
+    print(f"   鍒濆鏈�娲昏穬鐢ㄦ埛鏄电О: {initial_nickname}")
+    print()
+    
+    # 妯℃嫙鐢ㄦ埛鍙戣█
+    print("6. 妯℃嫙鐢ㄦ埛鍙戣█...")
+    # user1 鍙戣█ 5 娆�
+    for i in range(5):
+        group_stats_service.increment_user_message_count(test_group_id, test_users[0])
+        print(f"   {test_users[0]} 鍙戣█绗� {i+1} 娆�")
+    
+    # user2 鍙戣█ 3 娆�
+    for i in range(3):
+        group_stats_service.increment_user_message_count(test_group_id, test_users[1])
+        print(f"   {test_users[1]} 鍙戣█绗� {i+1} 娆�")
+    
+    # user3 鍙戣█ 7 娆�
+    for i in range(7):
+        group_stats_service.increment_user_message_count(test_group_id, test_users[2])
+        print(f"   {test_users[2]} 鍙戣█绗� {i+1} 娆�")
+    print()
+    
+    # 鏌ョ湅缁熻缁撴灉
+    print("7. 鏌ョ湅缁熻缁撴灉...")
+    final_stats = group_stats_service.get_group_message_stats(test_group_id)
+    final_nickname = group_stats_service.get_most_active_user_nickname(test_group_id)
+    print(f"   鏈�缁堢粺璁�: {final_stats}")
+    print(f"   鏈�娲昏穬鐢ㄦ埛鏄电О: {final_nickname}")
+    print()
+    
+    # 鑾峰彇缁熻鎽樿
+    print("8. 鑾峰彇缁熻鎽樿...")
+    summary = group_stats_service.get_group_stats_summary(test_group_id)
+    print(f"   缁熻鎽樿: {summary}")
+    print()
+    
+    # 娴嬭瘯鐩稿悓鍙戣█娆℃暟鐨勬儏鍐�
+    print("9. 娴嬭瘯鐩稿悓鍙戣█娆℃暟鐨勬儏鍐�...")
+    test_group_id_2 = "test_group_equal"
+    group_stats_service.clear_group_stats(test_group_id_2)
+    
+    # 涓や釜鐢ㄦ埛閮藉彂瑷� 3 娆�
+    for i in range(3):
+        group_stats_service.increment_user_message_count(test_group_id_2, "equal_user1")
+        group_stats_service.increment_user_message_count(test_group_id_2, "equal_user2")
+    
+    equal_stats = group_stats_service.get_group_message_stats(test_group_id_2)
+    equal_nickname = group_stats_service.get_most_active_user_nickname(test_group_id_2)
+    print(f"   鐩稿悓鍙戣█娆℃暟缁熻: {equal_stats}")
+    print(f"   鐩稿悓鍙戣█娆℃暟鏈�娲昏穬鐢ㄦ埛鏄电О: {equal_nickname}")
+    print()
+    
+    # 娴嬭瘯鏃犲彂瑷�鐨勬儏鍐�
+    print("10. 娴嬭瘯鏃犲彂瑷�鐨勬儏鍐�...")
+    test_group_id_3 = "test_group_empty"
+    group_stats_service.clear_group_stats(test_group_id_3)
+    
+    empty_stats = group_stats_service.get_group_message_stats(test_group_id_3)
+    empty_nickname = group_stats_service.get_most_active_user_nickname(test_group_id_3)
+    print(f"    鏃犲彂瑷�缁熻: {empty_stats}")
+    print(f"    鏃犲彂瑷�鏈�娲昏穬鐢ㄦ埛鏄电О: {empty_nickname}")
+    print()
+    
+    print("=== 娴嬭瘯瀹屾垚 ===")
+
+
+def test_config():
+    """娴嬭瘯閰嶇疆鍔熻兘"""
+    print("=== 閰嶇疆娴嬭瘯 ===\n")
+    
+    print(f"瀹㈡湇鍚嶇О鍒楄〃: {settings.customer_service_names}")
+    print(f"榛樿瀹㈡湇鍚嶇О: {settings.customer_service_default_name}")
+    print()
+
+
+if __name__ == "__main__":
+    try:
+        test_config()
+        test_group_stats()
+    except Exception as e:
+        print(f"娴嬭瘯澶辫触: {str(e)}")
+        import traceback
+        traceback.print_exc()
diff --git a/tests/test_message_processor.py b/tests/test_message_processor.py
index 5e1eee2..e149ce8 100644
--- a/tests/test_message_processor.py
+++ b/tests/test_message_processor.py
@@ -13,8 +13,13 @@
         """娴嬭瘯鍓嶅噯澶�"""
         self.processor = MessageProcessor()
     
-    def test_is_valid_group_message_success(self):
+    @patch('app.services.message_processor.silence_service')
+    @patch('app.services.message_processor.friend_ignore_service')
+    def test_is_valid_group_message_success(self, mock_friend_ignore_service, mock_silence_service):
         """娴嬭瘯鏈夋晥缇よ亰娑堟伅楠岃瘉"""
+        mock_silence_service.is_silence_active.return_value = False
+        mock_friend_ignore_service.is_friend_ignored.return_value = False
+
         callback_data = {
             "messageType": "80001",
             "data": {
@@ -24,7 +29,7 @@
                 "self": False
             }
         }
-        
+
         result = self.processor.is_valid_group_message(callback_data)
         assert result is True
     
@@ -72,9 +77,12 @@
         result = self.processor.is_valid_group_message(callback_data)
         assert result is False
 
+    @patch('app.services.message_processor.silence_service')
     @patch('app.services.message_processor.friend_ignore_service')
-    def test_is_valid_group_message_friend_ignored(self, mock_friend_ignore_service):
+    def test_is_valid_group_message_friend_ignored(self, mock_friend_ignore_service, mock_silence_service):
         """娴嬭瘯濂藉弸鍦ㄥ拷鐣ュ垪琛ㄤ腑鐨勬秷鎭�"""
+        mock_silence_service.is_silence_active.side_effect = [False, False]  # 涓ゆ妫�鏌ラ兘杩斿洖False
+        mock_silence_service.activate_silence_mode.return_value = True
         mock_friend_ignore_service.is_friend_ignored.return_value = True
 
         callback_data = {
@@ -91,9 +99,11 @@
         assert result is False
         mock_friend_ignore_service.is_friend_ignored.assert_called_once_with("wxid_test123")
 
+    @patch('app.services.message_processor.silence_service')
     @patch('app.services.message_processor.friend_ignore_service')
-    def test_is_valid_group_message_friend_not_ignored(self, mock_friend_ignore_service):
+    def test_is_valid_group_message_friend_not_ignored(self, mock_friend_ignore_service, mock_silence_service):
         """娴嬭瘯濂藉弸涓嶅湪蹇界暐鍒楄〃涓殑娑堟伅"""
+        mock_silence_service.is_silence_active.return_value = False
         mock_friend_ignore_service.is_friend_ignored.return_value = False
 
         callback_data = {
@@ -109,12 +119,14 @@
         result = self.processor.is_valid_group_message(callback_data)
         assert result is True
         mock_friend_ignore_service.is_friend_ignored.assert_called_once_with("wxid_test123")
-    
-    @patch('app.services.message_processor.redis_queue')
-    def test_enqueue_callback_message_success(self, mock_redis_queue):
-        """娴嬭瘯娑堟伅鍏ラ槦鎴愬姛"""
-        mock_redis_queue.enqueue_message.return_value = True
-        
+
+    @patch('app.services.message_processor.silence_service')
+    @patch('app.services.message_processor.friend_ignore_service')
+    def test_is_valid_group_message_silence_active(self, mock_friend_ignore_service, mock_silence_service):
+        """娴嬭瘯缇ょ粍闈欓粯妯″紡婵�娲绘椂鐨勬秷鎭鐞嗭紙闈炲拷鐣ュソ鍙嬶級"""
+        mock_friend_ignore_service.is_friend_ignored.return_value = False
+        mock_silence_service.is_silence_active.return_value = True
+
         callback_data = {
             "messageType": "80001",
             "data": {
@@ -124,9 +136,103 @@
                 "self": False
             }
         }
-        
+
+        result = self.processor.is_valid_group_message(callback_data)
+        assert result is False
+        mock_silence_service.is_silence_active.assert_called_once_with("group123@chatroom")
+
+    @patch('app.services.message_processor.silence_service')
+    @patch('app.services.message_processor.friend_ignore_service')
+    def test_is_valid_group_message_friend_ignored_activate_silence(self, mock_friend_ignore_service, mock_silence_service):
+        """娴嬭瘯濂藉弸琚拷鐣ユ椂婵�娲婚潤榛樻ā寮�"""
+        mock_silence_service.is_silence_active.side_effect = [False, False]  # 绗竴娆℃鏌ユ湭婵�娲伙紝绗簩娆℃鏌ュ拷鐣ュソ鍙嬫椂涔熸湭婵�娲�
+        mock_silence_service.activate_silence_mode.return_value = True
+        mock_friend_ignore_service.is_friend_ignored.return_value = True
+
+        callback_data = {
+            "messageType": "80001",
+            "data": {
+                "fromUser": "wxid_test123",
+                "fromGroup": "group123@chatroom",
+                "content": "娴嬭瘯娑堟伅",
+                "self": False
+            }
+        }
+
+        result = self.processor.is_valid_group_message(callback_data)
+        assert result is False
+        mock_silence_service.activate_silence_mode.assert_called_once_with("group123@chatroom")
+
+    @patch('app.services.message_processor.silence_service')
+    @patch('app.services.message_processor.friend_ignore_service')
+    def test_is_valid_group_message_friend_ignored_extend_silence(self, mock_friend_ignore_service, mock_silence_service):
+        """娴嬭瘯濂藉弸琚拷鐣ユ椂寤堕暱缇ょ粍闈欓粯妯″紡"""
+        mock_silence_service.is_silence_active.return_value = True  # 缇ょ粍闈欓粯妯″紡宸叉縺娲�
+        mock_silence_service.extend_silence_mode.return_value = True
+        mock_friend_ignore_service.is_friend_ignored.return_value = True
+
+        callback_data = {
+            "messageType": "80001",
+            "data": {
+                "fromUser": "wxid_test123",
+                "fromGroup": "group123@chatroom",
+                "content": "娴嬭瘯娑堟伅",
+                "self": False
+            }
+        }
+
+        result = self.processor.is_valid_group_message(callback_data)
+        assert result is False
+        mock_silence_service.extend_silence_mode.assert_called_once_with("group123@chatroom")
+
+    @patch('app.services.message_processor.silence_service')
+    @patch('app.services.message_processor.friend_ignore_service')
+    def test_is_valid_group_message_friend_ignored_in_silence_mode(self, mock_friend_ignore_service, mock_silence_service):
+        """娴嬭瘯缇ょ粍闈欓粯妯″紡涓嬪ソ鍙嬫秷鎭粛鑳藉埛鏂版椂闀�"""
+        # 妯℃嫙缇ょ粍闈欓粯妯″紡宸叉縺娲荤殑鎯呭喌
+        mock_friend_ignore_service.is_friend_ignored.return_value = True
+        mock_silence_service.is_silence_active.return_value = True
+        mock_silence_service.extend_silence_mode.return_value = True
+
+        callback_data = {
+            "messageType": "80001",
+            "data": {
+                "fromUser": "wxid_ignored_friend",
+                "fromGroup": "group123@chatroom",
+                "content": "琚拷鐣ュソ鍙嬪湪缇ょ粍闈欓粯妯″紡涓嬬殑娑堟伅",
+                "self": False
+            }
+        }
+
+        result = self.processor.is_valid_group_message(callback_data)
+        assert result is False
+
+        # 楠岃瘉濂藉弸蹇界暐妫�鏌ヨ璋冪敤
+        mock_friend_ignore_service.is_friend_ignored.assert_called_once_with("wxid_ignored_friend")
+        # 楠岃瘉缇ょ粍闈欓粯妯″紡鏃堕棿琚欢闀�
+        mock_silence_service.extend_silence_mode.assert_called_once_with("group123@chatroom")
+    
+    @patch('app.services.message_processor.silence_service')
+    @patch('app.services.message_processor.friend_ignore_service')
+    @patch('app.services.message_processor.redis_queue')
+    def test_enqueue_callback_message_success(self, mock_redis_queue, mock_friend_ignore_service, mock_silence_service):
+        """娴嬭瘯娑堟伅鍏ラ槦鎴愬姛"""
+        mock_silence_service.is_silence_active.return_value = False
+        mock_friend_ignore_service.is_friend_ignored.return_value = False
+        mock_redis_queue.enqueue_message.return_value = True
+
+        callback_data = {
+            "messageType": "80001",
+            "data": {
+                "fromUser": "wxid_test123",
+                "fromGroup": "group123@chatroom",
+                "content": "娴嬭瘯娑堟伅",
+                "self": False
+            }
+        }
+
         result = self.processor.enqueue_callback_message(callback_data)
-        
+
         assert result is True
         mock_redis_queue.enqueue_message.assert_called_once_with("wxid_test123", callback_data)
     
diff --git a/tests/test_online_status_monitor.py b/tests/test_online_status_monitor.py
new file mode 100644
index 0000000..c9368aa
--- /dev/null
+++ b/tests/test_online_status_monitor.py
@@ -0,0 +1,168 @@
+"""
+鍦ㄧ嚎鐘舵�佺洃鎺у姛鑳芥祴璇�
+"""
+
+import pytest
+import time
+from unittest.mock import Mock, patch
+from app.services.ecloud_client import ecloud_client
+from app.services.email_service import email_service
+from app.services.sms_service import sms_service
+from app.workers.online_status_worker import online_status_worker
+
+
+class TestOnlineStatusMonitor:
+    """鍦ㄧ嚎鐘舵�佺洃鎺ф祴璇�"""
+
+    def test_ecloud_query_online_wechat_list(self):
+        """娴嬭瘯鏌ヨ鍦ㄧ嚎寰俊鍒楄〃"""
+        # 妯℃嫙鏈夊湪绾垮井淇$殑鎯呭喌
+        with patch.object(ecloud_client.session, 'post') as mock_post:
+            mock_response = Mock()
+            mock_response.json.return_value = {
+                "code": "1000",
+                "message": "鎴愬姛",
+                "data": [
+                    {
+                        "wcId": "wxid_test123",
+                        "wId": "test-w-id-123"
+                    }
+                ]
+            }
+            mock_post.return_value = mock_response
+            
+            result = ecloud_client.query_online_wechat_list()
+            assert result is not None
+            assert len(result) == 1
+            assert result[0]["wcId"] == "wxid_test123"
+
+        # 妯℃嫙娌℃湁鍦ㄧ嚎寰俊鐨勬儏鍐�
+        with patch.object(ecloud_client.session, 'post') as mock_post:
+            mock_response = Mock()
+            mock_response.json.return_value = {
+                "code": "1000",
+                "message": "鎴愬姛",
+                "data": []
+            }
+            mock_post.return_value = mock_response
+            
+            result = ecloud_client.query_online_wechat_list()
+            assert result is not None
+            assert len(result) == 0
+
+    def test_sms_service_generate_sign(self):
+        """娴嬭瘯鐭俊鏈嶅姟绛惧悕鐢熸垚"""
+        # 浣跨敤鏂囨。涓殑绀轰緥鏁版嵁
+        sms_service.username = "test"
+        sms_service.password = "123"
+        timestamp = 1596254400000
+        
+        sign = sms_service._generate_sign(timestamp)
+        expected_sign = "e315cf297826abdeb2092cc57f29f0bf"
+        
+        assert sign == expected_sign
+
+    def test_email_service_disabled(self):
+        """娴嬭瘯閭欢鏈嶅姟绂佺敤鐘舵��"""
+        with patch('config.settings.email_enabled', False):
+            result = email_service.send_notification("娴嬭瘯涓婚", "娴嬭瘯鍐呭")
+            assert result is True  # 绂佺敤鏃跺簲璇ヨ繑鍥濼rue
+
+    def test_sms_service_disabled(self):
+        """娴嬭瘯鐭俊鏈嶅姟绂佺敤鐘舵��"""
+        with patch('config.settings.sms_enabled', False):
+            result = sms_service.send_notification("娴嬭瘯鍐呭")
+            assert result is True  # 绂佺敤鏃跺簲璇ヨ繑鍥濼rue
+
+    def test_online_status_worker_disabled(self):
+        """娴嬭瘯鍦ㄧ嚎鐘舵�佹娴嬪伐浣滆繘绋嬬鐢ㄧ姸鎬�"""
+        with patch('config.settings.online_status_enabled', False):
+            # 鍒涘缓鏂扮殑宸ヤ綔杩涚▼瀹炰緥杩涜娴嬭瘯
+            from app.workers.online_status_worker import OnlineStatusWorker
+            test_worker = OnlineStatusWorker()
+            
+            test_worker.start()
+            assert test_worker.running is False
+            assert test_worker.worker_thread is None
+
+    def test_online_status_worker_status(self):
+        """娴嬭瘯鑾峰彇宸ヤ綔杩涚▼鐘舵��"""
+        status = online_status_worker.get_status()
+
+        assert "running" in status
+        assert "enabled" in status
+        assert "check_interval_minutes" in status
+        assert "last_notification_time" in status
+        assert "notification_cooldown_minutes" in status
+
+        assert isinstance(status["running"], bool)
+        assert isinstance(status["enabled"], bool)
+        assert isinstance(status["check_interval_minutes"], (int, float))
+        assert isinstance(status["notification_cooldown_minutes"], (int, float))
+
+    def test_dynamic_w_id_update(self):
+        """娴嬭瘯鍔ㄦ�亀_id鏇存柊鍔熻兘"""
+        from config import settings
+
+        # 淇濆瓨鍘熷w_id
+        original_w_id = settings.get_current_w_id()
+
+        try:
+            # 娴嬭瘯鏇存柊w_id
+            new_w_id = "test-new-w-id-123"
+            success = settings.update_ecloud_w_id(new_w_id)
+
+            # 楠岃瘉鏇存柊鎴愬姛
+            assert success is True
+            assert settings.get_current_w_id() == new_w_id
+
+            # 娴嬭瘯鐩稿悓w_id涓嶄細閲嶅鏇存柊
+            success = settings.update_ecloud_w_id(new_w_id)
+            assert success is True
+
+        finally:
+            # 鎭㈠鍘熷w_id
+            settings.update_ecloud_w_id(original_w_id)
+
+    def test_w_id_update_in_online_status_check(self):
+        """娴嬭瘯鍦ㄧ嚎鐘舵�佹娴嬩腑鐨剋_id鏇存柊"""
+        from app.workers.online_status_worker import OnlineStatusWorker
+
+        # 鍒涘缓娴嬭瘯宸ヤ綔杩涚▼瀹炰緥
+        test_worker = OnlineStatusWorker()
+
+        # 妯℃嫙w_id鏇存柊
+        test_w_id = "test-w-id-456"
+        test_worker._update_w_id_if_needed(test_w_id)
+
+        # 杩欓噷涓昏娴嬭瘯鏂规硶涓嶄細鎶涘嚭寮傚父
+        # 瀹為檯鐨剋_id鏇存柊閫昏緫鍦╯ettings涓凡缁忔祴璇曡繃浜�
+
+    def test_startup_contact_sync_logic(self):
+        """娴嬭瘯鍚姩鏃惰仈绯讳汉鍚屾閫昏緫"""
+        # 娴嬭瘯娌℃湁鍦ㄧ嚎寰俊鏃剁殑澶勭悊閫昏緫
+        with patch.object(ecloud_client, 'query_online_wechat_list') as mock_query:
+            # 妯℃嫙鏌ヨ澶辫触
+            mock_query.return_value = None
+            # 杩欑鎯呭喌涓嬪簲璇ヨ烦杩囪仈绯讳汉鍚屾锛屼笉浼氭姏鍑哄紓甯�
+
+            # 妯℃嫙娌℃湁鍦ㄧ嚎寰俊
+            mock_query.return_value = []
+            # 杩欑鎯呭喌涓嬩篃搴旇璺宠繃鑱旂郴浜哄悓姝�
+
+            # 妯℃嫙鏈夊湪绾垮井淇�
+            mock_query.return_value = [{"wcId": "test_wc_id", "wId": "test_w_id"}]
+            # 杩欑鎯呭喌涓嬩細灏濊瘯鍚屾鑱旂郴浜�
+
+    def test_email_service_connection_handling(self):
+        """娴嬭瘯閭欢鏈嶅姟杩炴帴澶勭悊"""
+        # 娴嬭瘯閭欢鏈嶅姟鐨勮繛鎺ュ拰鍏抽棴閫昏緫
+        # 涓昏纭繚涓嶄細鍥犱负杩炴帴鍏抽棴寮傚父鑰屽奖鍝嶅彂閫佺粨鏋�
+
+        # 杩欓噷涓昏娴嬭瘯鏂规硶缁撴瀯锛屽疄闄呯殑SMTP娴嬭瘯闇�瑕佺湡瀹炵殑閭欢鏈嶅姟鍣�
+        assert hasattr(email_service, 'send_email')
+        assert hasattr(email_service, 'test_connection')
+
+
+if __name__ == "__main__":
+    pytest.main([__file__])
diff --git a/tests/test_silence_service.py b/tests/test_silence_service.py
new file mode 100644
index 0000000..181f9f2
--- /dev/null
+++ b/tests/test_silence_service.py
@@ -0,0 +1,177 @@
+"""
+闈欓粯妯″紡鏈嶅姟娴嬭瘯
+"""
+
+import time
+import pytest
+from unittest.mock import Mock, patch
+
+from app.services.silence_service import SilenceService
+
+
+class TestSilenceService:
+    """闈欓粯妯″紡鏈嶅姟娴嬭瘯绫�"""
+
+    def setup_method(self):
+        """娴嬭瘯鍓嶅噯澶�"""
+        self.service = SilenceService()
+
+    @patch('app.services.silence_service.settings')
+    @patch('app.services.silence_service.redis_queue')
+    def test_activate_silence_mode_success(self, mock_redis_queue, mock_settings):
+        """娴嬭瘯鎴愬姛婵�娲荤兢缁勯潤榛樻ā寮�"""
+        # 妯℃嫙閰嶇疆
+        mock_settings.silence_mode_enabled = True
+        mock_settings.silence_duration_minutes = 10
+
+        # 妯℃嫙Redis鎿嶄綔
+        mock_redis_client = Mock()
+        mock_redis_queue.redis_client = mock_redis_client
+        mock_redis_client.setex.return_value = True
+
+        # 娴嬭瘯婵�娲荤兢缁勯潤榛樻ā寮�
+        group_id = "test_group@chatroom"
+        result = self.service.activate_silence_mode(group_id)
+
+        assert result is True
+        # 楠岃瘉Redis璋冪敤
+        assert mock_redis_client.setex.call_count == 2  # 璁剧疆涓や釜閿�
+
+    @patch('app.services.silence_service.settings')
+    def test_activate_silence_mode_disabled(self, mock_settings):
+        """娴嬭瘯闈欓粯妯″紡鍔熻兘绂佺敤鏃剁殑琛屼负"""
+        mock_settings.silence_mode_enabled = False
+
+        group_id = "test_group@chatroom"
+        result = self.service.activate_silence_mode(group_id)
+        assert result is False
+
+    @patch('app.services.silence_service.settings')
+    @patch('app.services.silence_service.redis_queue')
+    def test_is_silence_active(self, mock_redis_queue, mock_settings):
+        """娴嬭瘯妫�鏌ラ潤榛樻ā寮忔槸鍚︽縺娲�"""
+        mock_settings.silence_mode_enabled = True
+        
+        # 妯℃嫙Redis鎿嶄綔
+        mock_redis_client = Mock()
+        mock_redis_queue.redis_client = mock_redis_client
+        
+        # 娴嬭瘯闈欓粯妯″紡婵�娲荤姸鎬�
+        mock_redis_client.exists.return_value = True
+        group_id = "test_group@chatroom"
+        result = self.service.is_silence_active(group_id)
+        assert result is True
+
+        # 娴嬭瘯闈欓粯妯″紡鏈縺娲荤姸鎬�
+        mock_redis_client.exists.return_value = False
+        result = self.service.is_silence_active(group_id)
+        assert result is False
+
+    @patch('app.services.silence_service.settings')
+    def test_is_silence_active_disabled(self, mock_settings):
+        """娴嬭瘯鍔熻兘绂佺敤鏃剁殑闈欓粯妯″紡妫�鏌�"""
+        mock_settings.silence_mode_enabled = False
+        
+        group_id = "test_group@chatroom"
+        result = self.service.is_silence_active(group_id)
+        assert result is False
+
+    @patch('app.services.silence_service.settings')
+    @patch('app.services.silence_service.redis_queue')
+    def test_get_silence_remaining_time(self, mock_redis_queue, mock_settings):
+        """娴嬭瘯鑾峰彇闈欓粯妯″紡鍓╀綑鏃堕棿"""
+        mock_settings.silence_mode_enabled = True
+        
+        # 妯℃嫙Redis鎿嶄綔
+        mock_redis_client = Mock()
+        mock_redis_queue.redis_client = mock_redis_client
+        
+        # 妯℃嫙闈欓粯妯″紡婵�娲�
+        mock_redis_client.exists.return_value = True
+        
+        # 妯℃嫙缁撴潫鏃堕棿锛堝綋鍓嶆椂闂� + 300绉掞級
+        future_time = time.time() + 300
+        mock_redis_client.get.return_value = str(future_time)
+        
+        group_id = "test_group@chatroom"
+        result = self.service.get_silence_remaining_time(group_id)
+        assert result is not None
+        assert result > 0
+        assert result <= 300
+
+    @patch('app.services.silence_service.settings')
+    @patch('app.services.silence_service.redis_queue')
+    def test_get_silence_remaining_time_inactive(self, mock_redis_queue, mock_settings):
+        """娴嬭瘯闈欓粯妯″紡鏈縺娲绘椂鑾峰彇鍓╀綑鏃堕棿"""
+        mock_settings.silence_mode_enabled = True
+        
+        # 妯℃嫙Redis鎿嶄綔
+        mock_redis_client = Mock()
+        mock_redis_queue.redis_client = mock_redis_client
+        mock_redis_client.exists.return_value = False
+        
+        group_id = "test_group@chatroom"
+        result = self.service.get_silence_remaining_time(group_id)
+        assert result is None
+
+    @patch('app.services.silence_service.settings')
+    @patch('app.services.silence_service.redis_queue')
+    def test_deactivate_silence_mode(self, mock_redis_queue, mock_settings):
+        """娴嬭瘯鎵嬪姩鍋滅敤闈欓粯妯″紡"""
+        # 妯℃嫙Redis鎿嶄綔
+        mock_redis_client = Mock()
+        mock_redis_queue.redis_client = mock_redis_client
+        mock_redis_client.delete.return_value = True
+        
+        result = self.service.deactivate_silence_mode()
+        assert result is True
+        
+        # 楠岃瘉鍒犻櫎浜嗕袱涓敭
+        assert mock_redis_client.delete.call_count == 2
+
+    @patch('app.services.silence_service.settings')
+    @patch('app.services.silence_service.redis_queue')
+    def test_extend_silence_mode(self, mock_redis_queue, mock_settings):
+        """娴嬭瘯寤堕暱闈欓粯妯″紡"""
+        mock_settings.silence_mode_enabled = True
+        mock_settings.silence_duration_minutes = 10
+        
+        # 妯℃嫙Redis鎿嶄綔
+        mock_redis_client = Mock()
+        mock_redis_queue.redis_client = mock_redis_client
+        mock_redis_client.setex.return_value = True
+        
+        group_id = "test_group@chatroom"
+        result = self.service.extend_silence_mode(group_id)
+        assert result is True
+
+    @patch('app.services.silence_service.settings')
+    def test_get_silence_status(self, mock_settings):
+        """娴嬭瘯鑾峰彇闈欓粯妯″紡鐘舵��"""
+        mock_settings.silence_mode_enabled = True
+        mock_settings.silence_duration_minutes = 10
+        
+        with patch.object(self.service, 'is_silence_active', return_value=False):
+            status = self.service.get_silence_status()
+            
+            assert status["enabled"] is True
+            assert status["active"] is False
+            assert status["duration_minutes"] == 10
+            assert status["remaining_seconds"] is None
+            assert status["remaining_minutes"] is None
+
+    @patch('app.services.silence_service.settings')
+    def test_get_silence_status_active(self, mock_settings):
+        """娴嬭瘯鑾峰彇婵�娲荤姸鎬佺殑闈欓粯妯″紡鐘舵��"""
+        mock_settings.silence_mode_enabled = True
+        mock_settings.silence_duration_minutes = 10
+        
+        with patch.object(self.service, 'is_silence_active', return_value=True), \
+             patch.object(self.service, 'get_silence_remaining_time', return_value=300):
+            
+            status = self.service.get_silence_status()
+            
+            assert status["enabled"] is True
+            assert status["active"] is True
+            assert status["remaining_seconds"] == 300
+            assert status["remaining_minutes"] == 5.0
diff --git "a/\347\237\255\344\277\241\345\217\221\351\200\201\346\216\245\345\217\243\346\226\207\346\241\243.txt" "b/\347\237\255\344\277\241\345\217\221\351\200\201\346\216\245\345\217\243\346\226\207\346\241\243.txt"
new file mode 100644
index 0000000..5b7b576
--- /dev/null
+++ "b/\347\237\255\344\277\241\345\217\221\351\200\201\346\216\245\345\217\243\346\226\207\346\241\243.txt"
@@ -0,0 +1,61 @@
+鎺ュ彛鍦板潃
+https://smsapi.izjun.com:8443/
+
+# 1.鍓嶈█
+鏈崗璁熀浜嶩TTP鏈嶅姟锛屼娇鐢≒OST璇锋眰鏂瑰紡锛岃姹傚拰搴旂瓟鍧囦负JSON鏍煎紡鏁版嵁.銆�
+瀛楁鍛藉悕鏂瑰紡锛氶┘宄版硶銆�
+缁熶竴璇锋眰鍜屽搷搴旂紪鐮侊細UTF-8
+缁熶竴璇锋眰Header鍐呭锛欳ontent-Type:application/json
+璇蜂娇鐢ㄦ帴鍙g綉鍏冲湴鍧�鏇挎崲鏂囨。涓殑鏈嶅姟鍣ㄥ湴鍧�锛歨ttp://{address:port}/sms
+ sign鍙傛暟璁$畻瑙勫垯锛氬涓寚瀹氬弬鏁板�肩粍鍚堟垚瀛楃涓插悗璁$畻MD532浣嶅皬鍐欑粨鏋�
+瑕佹眰锛歁D5(userName+timestamp+MD5(password))
+鍋囪锛歶serName(甯愬彿鍚�)=test
+ password(甯愬彿瀵嗙爜)=123
+ timestamp=1596254400000
+璁$畻锛歁D5(password)=202cb962ac59075b964b07152d234b70
+缁勫悎瀛楃涓诧細test1596254400000202cb962ac59075b964b07152d234b70
+sign缁撴灉锛歁D5(缁勫悎瀛楃涓�)=e315cf297826abdeb2092cc57f29f0bf
+
+# 2.鐭俊鎵归噺鍙戦�佹帴鍙�
+## 2.1璋冪敤鍦板潃
+鍦板潃锛歨ttp://{address:port}/sms/api/sendMessageMass
+璇锋眰鏂规硶锛歅OST
+
+## 2.2璇锋眰鍖呭ご瀹氫箟
+Accept:application/json
+Content-Type:application/json;charset=utf-8
+
+## 2.3璇锋眰鍙傛暟
+鍙傛暟鍚� 绫诲瀷 蹇呭~ 璇存槑
+userName String 鏄� 甯愬彿鐢ㄦ埛鍚�
+content String 鏄� 鐭俊鍐呭
+phoneList [Array] 鏄� 鍙戦�佹墜鏈哄彿鐮侊紝JSON鏁扮粍鏍煎紡銆�
+timestamp Long 鏄� 褰撳墠鏃堕棿鎴筹紝绮剧‘鍒版绉掋�備緥濡�2020骞�8鏈�1鏃�12:00:00鏃堕棿鎴充负锛�1596254400000
+sign String 鏄� 鐢变互涓嬪弬鏁板�肩粍鍚堟垚瀛楃涓插苟璁$畻MD5鍊硷紝鍙傝�冭缁嗚鍒� 璁$畻锛歁D5(userName+timestamp+MD5(password))
+
+## 2.4鍝嶅簲缁撴灉
+鍙傛暟鍚� 绫诲瀷 璇存槑
+code Integer 澶勭悊缁撴灉锛�0涓烘垚鍔燂紝鍏朵粬澶辫触锛岃缁嗗弬鑰冨搷搴旂姸鎬佺爜
+message String 澶勭悊缁撴灉鎻忚堪
+msgId Long 褰揷ode=0鏃讹紝绯荤粺杩斿洖鍞竴娑堟伅Id
+smsCount Integer 褰揷ode=0鏃讹紝绯荤粺杩斿洖娑堣�楄璐规�绘暟
+
+## 2.5璇锋眰绀轰緥
+鍙戦�佽姹傦細
+POSThttp://{address:port}/sms/api/sendMessageMass
+ Accept:application/json
+ Content-Type:application/json;charset=utf-8
+ {
+ "userName":"test",
+ "content":"銆愮鍚嶃�戞偍鐨勯獙璇佺爜鏄�123456",
+ "phoneList": ["13500000001","13500000002","13500000003"],
+ "timestamp":1596254400000,
+ "sign":"e315cf297826abdeb2092cc57f29f0bf"
+ }
+鍝嶅簲缁撴灉锛�
+{
+ "code":0,
+ "message":"澶勭悊鎴愬姛",
+ "msgId":123456,
+ "smsCount":3
+ }
\ No newline at end of file

--
Gitblit v1.9.1