From 99266ea57913663f9880c512726c42cb7e5e7f28 Mon Sep 17 00:00:00 2001 From: yj <2077506045@qq.com> Date: 星期一, 28 七月 2025 11:14:28 +0800 Subject: [PATCH] 新增忽略好友消息;删除多余文件 --- .gitignore | 4 tests/test_message_processor.py | 38 + app/api/friend_ignore.py | 241 +++++++++ tests/test_dify_streaming.py | 174 ++++++ README.md | 35 + logs/app.log | 13 /dev/null | 61 -- app/services/friend_ignore_service.py | 259 +++++++++ app/services/dify_client.py | 310 +++++++++++ app/services/contact_sync.py | 6 config.py | 99 +-- main.py | 17 tests/test_friend_ignore_service.py | 258 +++++++++ app/services/message_processor.py | 11 14 files changed, 1,383 insertions(+), 143 deletions(-) diff --git a/.env.example b/.env.example deleted file mode 100644 index 97e357c..0000000 --- a/.env.example +++ /dev/null @@ -1,22 +0,0 @@ -# 鏁版嵁搴撻厤缃� -DATABASE_URL=mysql+pymysql://username:password@localhost:3306/ecloud_dify - -# Redis閰嶇疆 -REDIS_URL=redis://localhost:6379/0 - -# E浜戠瀹堕厤缃� -ECLOUD_BASE_URL=http://your-ecloud-domain.com -ECLOUD_AUTHORIZATION=your-authorization-token - -# DifyAI閰嶇疆 -DIFY_BASE_URL=https://api.dify.ai/v1 -DIFY_API_KEY=your-dify-api-key - -# 鏈嶅姟閰嶇疆 -SERVER_HOST=0.0.0.0 -SERVER_PORT=8000 -DEBUG=True - -# 鏃ュ織閰嶇疆 -LOG_LEVEL=INFO -LOG_FILE=logs/app.log diff --git a/.gitignore b/.gitignore index 5b0371a..4bf7efb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # 蹇界暐鎵�鏈� .log 鏂囦欢 *.log +logs/*.log # 蹇界暐鐗瑰畾鏂囦欢 config.ini @@ -31,4 +32,5 @@ # 鍚屾椂蹇界暐鎵�鏈� .pyc 鏂囦欢锛堝彲閫変絾鎺ㄨ崘锛� *.pyc *.pyo -*.pyd \ No newline at end of file +*.pyd + diff --git a/CONFIG_GUIDE.md b/CONFIG_GUIDE.md deleted file mode 100644 index e7d48d4..0000000 --- a/CONFIG_GUIDE.md +++ /dev/null @@ -1,103 +0,0 @@ -# 閰嶇疆鏂囦欢浣跨敤鎸囧崡 - -## 姒傝堪 - -椤圭洰宸蹭粠Python閰嶇疆鏂囦欢锛坈onfig.py锛夎浆鎹负JSON閰嶇疆鏂囦欢锛坈onfig.json锛夛紝杩欐牱鏇撮�傚悎鎵撳寘涓篹xe鏂囦欢銆� - -## 閰嶇疆鏂囦欢缁撴瀯 - -### config.json -```json -{ - "database": { - "url": "mysql+pymysql://root:password@host:port/database" - }, - "redis": { - "url": "redis://localhost:6379/0" - }, - "ecloud": { - "base_url": "http://125.122.152.142:9899", - "authorization": "your_authorization_token" - }, - "dify": { - "base_url": "https://api.dify.ai/v1", - "api_key": "your_dify_api_key" - }, - "server": { - "host": "0.0.0.0", - "port": 7979, - "debug": true - }, - "logging": { - "level": "INFO", - "file": "logs/app.log" - }, - "message_processing": { - "max_retry_count": 3, - "retry_delay": 5, - "queue_timeout": 300 - } -} -``` - -## 閰嶇疆椤硅鏄� - -### 鏁版嵁搴撻厤缃� (database) -- `url`: 鏁版嵁搴撹繛鎺ュ瓧绗︿覆锛屾敮鎸丮ySQL - -### Redis閰嶇疆 (redis) -- `url`: Redis杩炴帴瀛楃涓� - -### E浜戠瀹堕厤缃� (ecloud) -- `base_url`: E浜戠瀹禔PI鍩虹URL -- `authorization`: E浜戠瀹禔PI鎺堟潈浠ょ墝 - -### DifyAI閰嶇疆 (dify) -- `base_url`: DifyAI API鍩虹URL -- `api_key`: DifyAI API瀵嗛挜 - -### 鏈嶅姟鍣ㄩ厤缃� (server) -- `host`: 鏈嶅姟鍣ㄧ洃鍚湴鍧� -- `port`: 鏈嶅姟鍣ㄧ洃鍚鍙� -- `debug`: 鏄惁鍚敤璋冭瘯妯″紡 - -### 鏃ュ織閰嶇疆 (logging) -- `level`: 鏃ュ織绾у埆 (DEBUG, INFO, WARNING, ERROR) -- `file`: 鏃ュ織鏂囦欢璺緞 - -### 娑堟伅澶勭悊閰嶇疆 (message_processing) -- `max_retry_count`: 鏈�澶ч噸璇曟鏁� -- `retry_delay`: 閲嶈瘯寤惰繜鏃堕棿锛堢锛� -- `queue_timeout`: 闃熷垪瓒呮椂鏃堕棿锛堢锛� - -## 浣跨敤鏂规硶 - -### 1. 淇敼閰嶇疆 -鐩存帴缂栬緫 `config.json` 鏂囦欢鍗冲彲锛屽簲鐢ㄤ細鑷姩鍔犺浇鏂伴厤缃�� - -### 2. 閰嶇疆鏂囦欢浣嶇疆 -- 寮�鍙戠幆澧冿細椤圭洰鏍圭洰褰曚笅鐨� `config.json` -- 鐢熶骇鐜锛歟xe鏂囦欢鍚岀洰褰曚笅鐨� `config.json` - -### 3. 閰嶇疆楠岃瘉 -濡傛灉閰嶇疆鏂囦欢涓嶅瓨鍦ㄦ垨鏍煎紡閿欒锛岀郴缁熶細浣跨敤榛樿閰嶇疆骞惰緭鍑洪敊璇俊鎭�� - -## 鍏煎鎬� - -- 鍘熸湁鐨� `from config import settings` 瀵煎叆鏂瑰紡淇濇寔涓嶅彉 -- 鎵�鏈夐厤缃睘鎬х殑璁块棶鏂瑰紡淇濇寔涓嶅彉锛堝 `settings.database_url`锛� -- 鍚戝悗鍏煎锛屼笉闇�瑕佷慨鏀圭幇鏈変唬鐮� - -## 鎵撳寘涓篹xe鐨勪紭鍔� - -1. **閰嶇疆澶栭儴鍖�**: 閰嶇疆鏂囦欢鐙珛浜巈xe鏂囦欢锛屼究浜庨儴缃叉椂淇敼 -2. **鏃犻渶閲嶆柊缂栬瘧**: 淇敼閰嶇疆涓嶉渶瑕侀噸鏂版墦鍖卐xe -3. **鏄撲簬缁存姢**: JSON鏍煎紡鐩磋鏄撹锛屼究浜庤繍缁翠汉鍛橀厤缃� -4. **鐗堟湰鎺у埗鍙嬪ソ**: 鍙互涓轰笉鍚岀幆澧冨噯澶囦笉鍚岀殑閰嶇疆鏂囦欢 - -## 娉ㄦ剰浜嬮」 - -1. 纭繚 `config.json` 鏂囦欢鏍煎紡姝g‘锛屽彲浠ヤ娇鐢↗SON楠岃瘉宸ュ叿妫�鏌� -2. 鏁忔劅淇℃伅锛堝鏁版嵁搴撳瘑鐮併�丄PI瀵嗛挜锛夊簲濡ュ杽淇濈 -3. 鐢熶骇鐜寤鸿灏� `debug` 璁剧疆涓� `false` -4. 鏃ュ織鏂囦欢璺緞纭繚搴旂敤鏈夊啓鍏ユ潈闄� diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index 8fcd1d5..0000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,246 +0,0 @@ -# 閮ㄧ讲鎸囧崡 - -## 閮ㄧ讲鍓嶆鏌ユ竻鍗� - -### 1. 鐜瑕佹眰 -- [ ] Python 3.11+ 宸插畨瑁� -- [ ] Docker 鍜� Docker Compose 宸插畨瑁咃紙鎺ㄨ崘锛� -- [ ] MySQL 8.0+ 鍙敤 -- [ ] Redis 7+ 鍙敤 -- [ ] 缃戠粶绔彛 8000 鍙闂� - -### 2. 閰嶇疆鏂囦欢 -- [ ] 澶嶅埗 `.env.example` 涓� `.env` -- [ ] 閰嶇疆鏁版嵁搴撹繛鎺ヤ俊鎭� -- [ ] 閰嶇疆Redis杩炴帴淇℃伅 -- [ ] 閰嶇疆E浜戠瀹禔PI淇℃伅 -- [ ] 閰嶇疆DifyAI API瀵嗛挜 - -### 3. 蹇呰鐨凙PI淇℃伅 -- [ ] E浜戠瀹跺煙鍚嶅拰鎺堟潈token -- [ ] DifyAI API瀵嗛挜 -- [ ] 纭E浜戠瀹跺洖璋僓RL閰嶇疆 - -## 蹇�熼儴缃诧紙Docker鏂瑰紡锛� - -### 1. 鍏嬮殕椤圭洰 -```bash -git clone <repository-url> -cd ECloud -``` - -### 2. 閰嶇疆鐜鍙橀噺 -```bash -cp .env.example .env -# 缂栬緫 .env 鏂囦欢锛屽~鍏ユ纭殑閰嶇疆淇℃伅 -``` - -### 3. 鍚姩鏈嶅姟 -```bash -# 鍚姩鎵�鏈夋湇鍔★紙MySQL銆丷edis銆佸簲鐢級 -docker-compose up -d - -# 鏌ョ湅鍚姩鏃ュ織 -docker-compose logs -f app -``` - -### 4. 楠岃瘉閮ㄧ讲 -```bash -# 杩愯闆嗘垚娴嬭瘯 -python test_integration.py - -# 妫�鏌ユ湇鍔$姸鎬� -curl http://localhost:8000/api/v1/health -``` - -## 鎵嬪姩閮ㄧ讲 - -### 1. 瀹夎渚濊禆 -```bash -# 鍒涘缓铏氭嫙鐜 -python3 -m venv venv -source venv/bin/activate - -# 瀹夎渚濊禆 -pip install -r requirements.txt -``` - -### 2. 鍚姩鏁版嵁搴撴湇鍔� -```bash -# 浣跨敤Docker鍚姩MySQL鍜孯edis -docker-compose up -d mysql redis - -# 鎴栬�呬娇鐢ㄧ郴缁熸湇鍔� -sudo systemctl start mysql -sudo systemctl start redis -``` - -### 3. 鍒濆鍖栨暟鎹簱 -```bash -python app/utils/database_init.py -``` - -### 4. 鍚姩搴旂敤 -```bash -# 浣跨敤鍚姩鑴氭湰 -chmod +x start.sh -./start.sh - -# 鎴栬�呯洿鎺ュ惎鍔� -python main.py -``` - -## 鐢熶骇鐜閮ㄧ讲 - -### 1. 浣跨敤杩涚▼绠$悊鍣� -```bash -# 瀹夎supervisor -sudo apt-get install supervisor - -# 鍒涘缓閰嶇疆鏂囦欢 /etc/supervisor/conf.d/ecloud-dify.conf -[program:ecloud-dify] -command=/path/to/venv/bin/python /path/to/main.py -directory=/path/to/project -user=www-data -autostart=true -autorestart=true -redirect_stderr=true -stdout_logfile=/var/log/ecloud-dify.log -``` - -### 2. 浣跨敤Nginx鍙嶅悜浠g悊 -```nginx -server { - listen 80; - server_name your-domain.com; - - location / { - proxy_pass http://127.0.0.1:8000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -### 3. 閰嶇疆HTTPS -```bash -# 浣跨敤Let's Encrypt -sudo certbot --nginx -d your-domain.com -``` - -## 鐩戞帶鍜岀淮鎶� - -### 1. 鏃ュ織鐩戞帶 -```bash -# 鏌ョ湅搴旂敤鏃ュ織 -tail -f logs/app.log - -# 鏌ョ湅閿欒鏃ュ織 -tail -f logs/app_error.log - -# 浣跨敤Docker鏌ョ湅鏃ュ織 -docker-compose logs -f app -``` - -### 2. 鎬ц兘鐩戞帶 -```bash -# 鏌ョ湅Redis闃熷垪鐘舵�� -redis-cli -> KEYS ecloud_queue:* -> LLEN ecloud_queue:鐢ㄦ埛ID - -# 鏌ョ湅鏁版嵁搴撹繛鎺� -mysql -u ecloud -p ecloud_dify -> SHOW PROCESSLIST; -``` - -### 3. 鍋ュ悍妫�鏌� -```bash -# API鍋ュ悍妫�鏌� -curl http://localhost:8000/api/v1/health - -# 鏁版嵁搴撳仴搴锋鏌� -python -c "from app.utils.database_init import check_database_health; print(check_database_health())" -``` - -## 鏁呴殰鎺掗櫎 - -### 甯歌闂 - -1. **绔彛琚崰鐢�** - ```bash - # 鏌ョ湅绔彛鍗犵敤 - netstat -tlnp | grep 8000 - - # 淇敼閰嶇疆鏂囦欢涓殑绔彛 - vim .env - ``` - -2. **鏁版嵁搴撹繛鎺ュけ璐�** - ```bash - # 妫�鏌ySQL鏈嶅姟鐘舵�� - sudo systemctl status mysql - - # 妫�鏌ヨ繛鎺ラ厤缃� - mysql -h localhost -u ecloud -p - ``` - -3. **Redis杩炴帴澶辫触** - ```bash - # 妫�鏌edis鏈嶅姟鐘舵�� - sudo systemctl status redis - - # 娴嬭瘯杩炴帴 - redis-cli ping - ``` - -4. **DifyAI API璋冪敤澶辫触** - - 妫�鏌PI瀵嗛挜鏄惁姝g‘ - - 纭缃戠粶杩炴帴 - - 鏌ョ湅API閰嶉闄愬埗 - -### 鏃ュ織鍒嗘瀽 - -閲嶈鏃ュ織鍏抽敭璇嶏細 -- `ERROR`: 閿欒淇℃伅 -- `娑堟伅澶勭悊澶辫触`: 娑堟伅澶勭悊寮傚父 -- `鏁版嵁搴撹繛鎺ュけ璐: 鏁版嵁搴撻棶棰� -- `Dify鍝嶅簲澶辫触`: DifyAI API闂 -- `鍙戦�佹枃鏈秷鎭け璐: E浜戠瀹禔PI闂 - -## 澶囦唤鍜屾仮澶� - -### 鏁版嵁搴撳浠� -```bash -# 澶囦唤鏁版嵁搴� -mysqldump -u ecloud -p ecloud_dify > backup_$(date +%Y%m%d).sql - -# 鎭㈠鏁版嵁搴� -mysql -u ecloud -p ecloud_dify < backup_20231201.sql -``` - -### Redis鏁版嵁澶囦唤 -```bash -# Redis浼氳嚜鍔ㄤ繚瀛樺埌dump.rdb鏂囦欢 -cp /var/lib/redis/dump.rdb backup_redis_$(date +%Y%m%d).rdb -``` - -## 鎵╁睍鍜屼紭鍖� - -### 1. 姘村钩鎵╁睍 -- 浣跨敤澶氫釜搴旂敤瀹炰緥 -- 閰嶇疆璐熻浇鍧囪 鍣� -- 浣跨敤Redis闆嗙兢 - -### 2. 鎬ц兘浼樺寲 -- 璋冩暣鏁版嵁搴撹繛鎺ユ睜澶у皬 -- 浼樺寲Redis闃熷垪澶勭悊 -- 浣跨敤缂撳瓨鍑忓皯API璋冪敤 - -### 3. 瀹夊叏鍔犲浐 -- 閰嶇疆闃茬伀澧欒鍒� -- 浣跨敤HTTPS -- 瀹氭湡鏇存柊渚濊禆鍖� -- 閰嶇疆璁块棶鎺у埗 diff --git a/DEPLOYMENT_WINDOWS.md b/DEPLOYMENT_WINDOWS.md deleted file mode 100644 index 797b74d..0000000 --- a/DEPLOYMENT_WINDOWS.md +++ /dev/null @@ -1,186 +0,0 @@ -# E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 Windows閮ㄧ讲鎸囧崡 - -## 姒傝堪 - -鏈枃妗d粙缁嶅浣曞湪Windows鏈嶅姟鍣ㄤ笂閮ㄧ讲E浜戠瀹�-DifyAI瀵规帴鏈嶅姟鐨別xe鐗堟湰銆� - -## 绯荤粺瑕佹眰 - -- Windows Server 2016 鎴栨洿楂樼増鏈� -- Windows 10/11 (鐢ㄤ簬娴嬭瘯) -- 鑷冲皯 2GB 鍙敤鍐呭瓨 -- 鑷冲皯 1GB 鍙敤纾佺洏绌洪棿 -- 缃戠粶杩炴帴锛堢敤浜庤闂暟鎹簱銆丷edis銆丒浜戠瀹跺拰DifyAI鏈嶅姟锛� - -## 閮ㄧ讲姝ラ - -### 1. 鍑嗗閮ㄧ讲鏂囦欢 - -纭繚浠ヤ笅鏂囦欢瀛樺湪浜庨儴缃茬洰褰曚腑锛� -- `ecloud_dify.exe` - 涓荤▼搴忔枃浠� -- `config.production.json` - 鐢熶骇鐜閰嶇疆妯℃澘 -- `config.example.json` - 閰嶇疆绀轰緥鏂囦欢 -- `install_service.bat` - Windows鏈嶅姟瀹夎鑴氭湰 -- `uninstall_service.bat` - Windows鏈嶅姟鍗歌浇鑴氭湰 -- `start.bat` - 鐩存帴鍚姩鑴氭湰锛堥潪鏈嶅姟妯″紡锛� - -### 2. 閰嶇疆鏈嶅姟 - -#### 2.1 鍒涘缓閰嶇疆鏂囦欢 - -1. 澶嶅埗 `config.production.json` 涓� `config.json` -2. 缂栬緫 `config.json` 鏂囦欢锛屼慨鏀逛互涓嬮厤缃細 - -```json -{ - "database": { - "url": "mysql+pymysql://鐢ㄦ埛鍚�:瀵嗙爜@鏁版嵁搴撳湴鍧�:绔彛/鏁版嵁搴撳悕" - }, - "redis": { - "url": "redis://Redis鍦板潃:绔彛/鏁版嵁搴撶紪鍙�" - }, - "ecloud": { - "base_url": "E浜戠瀹禔PI鍦板潃", - "authorization": "E浜戠瀹舵巿鏉冧护鐗�" - }, - "dify": { - "base_url": "DifyAI API鍦板潃", - "api_key": "DifyAI API瀵嗛挜" - }, - "server": { - "host": "0.0.0.0", - "port": 7979, - "debug": false - } -} -``` - -#### 2.2 閰嶇疆璇存槑 - -- **database.url**: MySQL鏁版嵁搴撹繛鎺ュ瓧绗︿覆 -- **redis.url**: Redis鏈嶅姟鍣ㄨ繛鎺ュ瓧绗︿覆 -- **ecloud**: E浜戠瀹剁浉鍏抽厤缃� -- **dify**: DifyAI鐩稿叧閰嶇疆 -- **server**: 鏈嶅姟鍣ㄧ洃鍚厤缃� - -### 3. 瀹夎鍜屽惎鍔ㄦ湇鍔� - -#### 鏂瑰紡涓�锛歐indows鏈嶅姟妯″紡锛堟帹鑽愶級 - -1. **瀹夎鏈嶅姟**锛� - - 鍙抽敭鐐瑰嚮 `install_service.bat` - - 閫夋嫨"浠ョ鐞嗗憳韬唤杩愯" - - 鎸夌収鎻愮ず瀹屾垚瀹夎 - -2. **绠$悊鏈嶅姟**锛� - ```cmd - # 鍚姩鏈嶅姟 - sc start ECloudDifyService - - # 鍋滄鏈嶅姟 - sc stop ECloudDifyService - - # 鏌ョ湅鏈嶅姟鐘舵�� - sc query ECloudDifyService - - # 鍒犻櫎鏈嶅姟 - sc delete ECloudDifyService - ``` - -3. **鍗歌浇鏈嶅姟**锛� - - 鍙抽敭鐐瑰嚮 `uninstall_service.bat` - - 閫夋嫨"浠ョ鐞嗗憳韬唤杩愯" - -#### 鏂瑰紡浜岋細鐩存帴杩愯妯″紡 - -1. 鍙屽嚮 `start.bat` 鍚姩鏈嶅姟 -2. 鎸� Ctrl+C 鍋滄鏈嶅姟 - -### 4. 楠岃瘉閮ㄧ讲 - -1. **妫�鏌ユ湇鍔$姸鎬�**锛� - - 璁块棶 `http://鏈嶅姟鍣↖P:7979/health` - - 搴旇杩斿洖鍋ュ悍妫�鏌ヤ俊鎭� - -2. **鏌ョ湅鏃ュ織**锛� - - 鏃ュ織鏂囦欢浣嶄簬 `logs/app.log` - - 妫�鏌ユ槸鍚︽湁閿欒淇℃伅 - -3. **娴嬭瘯API**锛� - - 璁块棶 `http://鏈嶅姟鍣↖P:7979/` - - 搴旇杩斿洖鏈嶅姟淇℃伅 - -## 闃茬伀澧欓厤缃� - -纭繚Windows闃茬伀澧欏厑璁哥鍙�7979鐨勫叆绔欒繛鎺ワ細 - -```cmd -# 娣诲姞闃茬伀澧欒鍒� -netsh advfirewall firewall add rule name="ECloudDify Service" dir=in action=allow protocol=TCP localport=7979 -``` - -## 鏁呴殰鎺掗櫎 - -### 甯歌闂 - -1. **鏈嶅姟鍚姩澶辫触**锛� - - 妫�鏌ラ厤缃枃浠舵牸寮忔槸鍚︽纭� - - 纭鏁版嵁搴撳拰Redis杩炴帴鏄惁姝e父 - - 鏌ョ湅鏃ュ織鏂囦欢鑾峰彇璇︾粏閿欒淇℃伅 - -2. **绔彛琚崰鐢�**锛� - - 淇敼閰嶇疆鏂囦欢涓殑绔彛鍙� - - 鎴栬�呭仠姝㈠崰鐢ㄧ鍙g殑鍏朵粬绋嬪簭 - -3. **鏉冮檺闂**锛� - - 纭繚浠ョ鐞嗗憳韬唤杩愯瀹夎鑴氭湰 - - 妫�鏌xe鏂囦欢鐨勬墽琛屾潈闄� - -4. **缃戠粶杩炴帴闂**锛� - - 妫�鏌ラ槻鐏璁剧疆 - - 纭缃戠粶杩炴帴姝e父 - -### 鏃ュ織鏌ョ湅 - -鏃ュ織鏂囦欢浣嶇疆锛歚logs/app.log` - -甯哥敤鏃ュ織绾у埆锛� -- INFO: 涓�鑸俊鎭� -- WARNING: 璀﹀憡淇℃伅 -- ERROR: 閿欒淇℃伅 - -## 鎬ц兘浼樺寲 - -1. **鍐呭瓨浼樺寲**锛� - - 鐩戞帶鍐呭瓨浣跨敤鎯呭喌 - - 蹇呰鏃惰皟鏁寸郴缁熷唴瀛樺垎閰� - -2. **缃戠粶浼樺寲**锛� - - 纭繚缃戠粶寤惰繜杈冧綆 - - 浣跨敤楂橀�熺綉缁滆繛鎺� - -3. **鏁版嵁搴撲紭鍖�**锛� - - 浼樺寲鏁版嵁搴撹繛鎺ユ睜璁剧疆 - - 瀹氭湡娓呯悊鏃ュ織鏁版嵁 - -## 瀹夊叏寤鸿 - -1. **閰嶇疆鏂囦欢瀹夊叏**锛� - - 淇濇姢閰嶇疆鏂囦欢涓殑鏁忔劅淇℃伅 - - 璁剧疆閫傚綋鐨勬枃浠舵潈闄� - -2. **缃戠粶瀹夊叏**锛� - - 浣跨敤HTTPS锛堝鏋滄敮鎸侊級 - - 闄愬埗璁块棶IP鑼冨洿 - -3. **瀹氭湡鏇存柊**锛� - - 瀹氭湡鏇存柊鏈嶅姟鐗堟湰 - - 鍏虫敞瀹夊叏琛ヤ竵 - -## 鑱旂郴鏀寔 - -濡傞亣鍒伴棶棰橈紝璇锋彁渚涗互涓嬩俊鎭細 -- 閿欒鏃ュ織鍐呭 -- 閰嶇疆鏂囦欢锛堥殣钘忔晱鎰熶俊鎭級 -- 绯荤粺鐜淇℃伅 -- 闂澶嶇幇姝ラ diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b820892..0000000 --- a/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM python:3.11-slim - -# 璁剧疆宸ヤ綔鐩綍 -WORKDIR /app - -# 瀹夎绯荤粺渚濊禆 -RUN apt-get update && apt-get install -y \ - gcc \ - default-libmysqlclient-dev \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* - -# 澶嶅埗渚濊禆鏂囦欢 -COPY requirements.txt . - -# 瀹夎Python渚濊禆 -RUN pip install --no-cache-dir -r requirements.txt - -# 澶嶅埗搴旂敤浠g爜 -COPY . . - -# 鍒涘缓鏃ュ織鐩綍 -RUN mkdir -p logs - -# 鏆撮湶绔彛 -EXPOSE 8000 - -# 鍚姩鍛戒护 -CMD ["python", "main.py"] diff --git a/README.md b/README.md index 9170a7a..e8f8ccc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ - 杩囨护鍜岄獙璇佹秷鎭紙浠呭鐞嗙兢鑱婃秷鎭紝蹇界暐鑷繁鍙戦�佺殑娑堟伅锛� - 浣跨敤Redis闃熷垪绠$悊鐢ㄦ埛娑堟伅锛岄槻姝㈠苟鍙戝鐞� - 鑷姩鑾峰彇鍜屼繚瀛樿仈绯讳汉淇℃伅 -- 璋冪敤DifyAI鎺ュ彛鑾峰彇AI鍥炵瓟 +- **鏀寔DifyAI娴佸紡鍜岄樆濉炰袱绉嶆ā寮�** + - 娴佸紡妯″紡锛氬疄鏃舵帴鏀禔I鍥炲锛屽搷搴旀洿蹇� + - 闃诲妯″紡锛氱瓑寰呭畬鏁村洖澶嶅悗杩斿洖锛屾洿绋冲畾 - 灏咥I鍥炵瓟鍙戦�佸洖缇よ亰 - 瀹屾暣鐨勬棩蹇楄褰曞拰閿欒澶勭悊 @@ -154,11 +156,34 @@ ## 閰嶇疆璇存槑 -涓昏閰嶇疆椤瑰湪 `config.py` 涓細 +涓昏閰嶇疆椤瑰湪 `config.json` 涓細 -- `max_retry_count`: 鏈�澶ч噸璇曟鏁帮紙榛樿3娆★級 -- `retry_delay`: 閲嶈瘯寤惰繜锛堥粯璁�5绉掞級 -- `queue_timeout`: 闃熷垪瓒呮椂鏃堕棿锛堥粯璁�300绉掞級 +### DifyAI閰嶇疆 +- `dify.streaming_enabled`: 鏄惁鍚敤娴佸紡妯″紡锛堥粯璁わ細false锛� +- `dify.streaming_timeout`: 娴佸紡璇锋眰瓒呮椂鏃堕棿锛屽崟浣嶇锛堥粯璁わ細120锛� +- `dify.base_url`: DifyAI API鍦板潃 +- `dify.api_key`: DifyAI API瀵嗛挜 + +### 娑堟伅澶勭悊閰嶇疆 +- `message_processing.max_retry_count`: 鏈�澶ч噸璇曟鏁帮紙榛樿3娆★級 +- `message_processing.retry_delay`: 閲嶈瘯寤惰繜锛堥粯璁�5绉掞級 +- `message_processing.queue_timeout`: 闃熷垪瓒呮椂鏃堕棿锛堥粯璁�300绉掞級 + +### 娴佸紡妯″紡璇存槑 +**鍚敤娴佸紡妯″紡鐨勪紭鍔匡細** +- 瀹炴椂鍝嶅簲锛氳竟鐢熸垚杈硅繑鍥烇紝鐢ㄦ埛浣撻獙鏇村ソ +- 鏇村揩鎰熺煡锛氭棤闇�绛夊緟瀹屾暣鍥炲鍗冲彲寮�濮嬪鐞� +- 杩炴帴淇濇椿锛氳嚜鍔ㄥ鐞唒ing浜嬩欢锛屼繚鎸佽繛鎺ョǔ瀹� + +**閰嶇疆绀轰緥锛�** +```json +{ + "dify": { + "streaming_enabled": true, + "streaming_timeout": 180 + } +} +``` ## 鏃ュ織绠$悊 diff --git a/app/api/friend_ignore.py b/app/api/friend_ignore.py new file mode 100644 index 0000000..4f8c68a --- /dev/null +++ b/app/api/friend_ignore.py @@ -0,0 +1,241 @@ +""" +濂藉弸蹇界暐鍒楄〃绠$悊API +""" + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from typing import List, Set +from loguru import logger + +from app.services.friend_ignore_service import friend_ignore_service +from app.services.contact_sync import contact_sync_service +from config import settings + + +router = APIRouter() + + +class AddFriendsRequest(BaseModel): + """娣诲姞濂藉弸鍒板拷鐣ュ垪琛ㄨ姹傛ā鍨�""" + friends: List[str] + + +class RemoveFriendRequest(BaseModel): + """浠庡拷鐣ュ垪琛ㄧЩ闄ゅソ鍙嬭姹傛ā鍨�""" + w_id: str + + +class IgnoreListResponse(BaseModel): + """蹇界暐鍒楄〃鍝嶅簲妯″瀷""" + success: bool + message: str + data: Set[str] = None + count: int = 0 + + +@router.get("/ignore-list", response_model=IgnoreListResponse) +async def get_ignore_list(): + """ + 鑾峰彇褰撳墠鐨勫ソ鍙嬪拷鐣ュ垪琛� + + Returns: + 蹇界暐鍒楄〃鍝嶅簲 + """ + try: + ignore_list = friend_ignore_service.get_ignore_list() + count = friend_ignore_service.get_ignore_list_count() + + return IgnoreListResponse( + success=True, + message="鑾峰彇蹇界暐鍒楄〃鎴愬姛", + data=ignore_list, + count=count + ) + except Exception as e: + logger.error(f"鑾峰彇蹇界暐鍒楄〃澶辫触: {str(e)}") + raise HTTPException(status_code=500, detail=f"鑾峰彇蹇界暐鍒楄〃澶辫触: {str(e)}") + + +@router.post("/ignore-list/add", response_model=IgnoreListResponse) +async def add_friends_to_ignore_list(request: AddFriendsRequest): + """ + 娣诲姞濂藉弸鍒板拷鐣ュ垪琛� + + Args: + request: 娣诲姞濂藉弸璇锋眰 + + Returns: + 鎿嶄綔缁撴灉 + """ + try: + success = friend_ignore_service.add_friends_to_ignore_list(request.friends) + + if success: + count = friend_ignore_service.get_ignore_list_count() + return IgnoreListResponse( + success=True, + message=f"鎴愬姛娣诲姞 {len(request.friends)} 涓ソ鍙嬪埌蹇界暐鍒楄〃", + count=count + ) + else: + raise HTTPException(status_code=400, detail="娣诲姞濂藉弸鍒板拷鐣ュ垪琛ㄥけ璐�") + + except Exception as e: + logger.error(f"娣诲姞濂藉弸鍒板拷鐣ュ垪琛ㄥけ璐�: {str(e)}") + raise HTTPException(status_code=500, detail=f"娣诲姞濂藉弸鍒板拷鐣ュ垪琛ㄥけ璐�: {str(e)}") + + +@router.post("/ignore-list/remove", response_model=IgnoreListResponse) +async def remove_friend_from_ignore_list(request: RemoveFriendRequest): + """ + 浠庡拷鐣ュ垪琛ㄤ腑绉婚櫎濂藉弸 + + Args: + request: 绉婚櫎濂藉弸璇锋眰 + + Returns: + 鎿嶄綔缁撴灉 + """ + try: + success = friend_ignore_service.remove_friend_from_ignore_list(request.w_id) + + if success: + count = friend_ignore_service.get_ignore_list_count() + return IgnoreListResponse( + success=True, + message=f"鎴愬姛浠庡拷鐣ュ垪琛ㄤ腑绉婚櫎濂藉弸: {request.w_id}", + count=count + ) + else: + raise HTTPException(status_code=400, detail="浠庡拷鐣ュ垪琛ㄧЩ闄ゅソ鍙嬪け璐�") + + except Exception as e: + logger.error(f"浠庡拷鐣ュ垪琛ㄧЩ闄ゅソ鍙嬪け璐�: {str(e)}") + raise HTTPException(status_code=500, detail=f"浠庡拷鐣ュ垪琛ㄧЩ闄ゅソ鍙嬪け璐�: {str(e)}") + + +@router.delete("/ignore-list", response_model=IgnoreListResponse) +async def clear_ignore_list(): + """ + 娓呯┖蹇界暐鍒楄〃 + + Returns: + 鎿嶄綔缁撴灉 + """ + try: + success = friend_ignore_service.clear_ignore_list() + + if success: + return IgnoreListResponse( + success=True, + message="鎴愬姛娓呯┖蹇界暐鍒楄〃", + count=0 + ) + else: + raise HTTPException(status_code=400, detail="娓呯┖蹇界暐鍒楄〃澶辫触") + + except Exception as e: + logger.error(f"娓呯┖蹇界暐鍒楄〃澶辫触: {str(e)}") + raise HTTPException(status_code=500, detail=f"娓呯┖蹇界暐鍒楄〃澶辫触: {str(e)}") + + +@router.post("/sync-contacts", response_model=IgnoreListResponse) +async def sync_contacts_and_rebuild_ignore_list(): + """ + 閲嶆柊鍚屾鑱旂郴浜哄苟閲嶅缓蹇界暐鍒楄〃 + + Returns: + 鎿嶄綔缁撴灉 + """ + try: + if not settings.ecloud_w_id: + raise HTTPException(status_code=400, detail="鏈厤缃甧cloud_w_id") + + success = contact_sync_service.sync_contacts_on_startup(settings.ecloud_w_id) + + if success: + count = friend_ignore_service.get_ignore_list_count() + return IgnoreListResponse( + success=True, + message="鑱旂郴浜哄悓姝ュ畬鎴愶紝蹇界暐鍒楄〃宸查噸寤�", + count=count + ) + else: + raise HTTPException(status_code=400, detail="鑱旂郴浜哄悓姝ュけ璐�") + + except Exception as e: + logger.error(f"鑱旂郴浜哄悓姝ュけ璐�: {str(e)}") + raise HTTPException(status_code=500, detail=f"鑱旂郴浜哄悓姝ュけ璐�: {str(e)}") + + +@router.get("/ignore-list/check/{w_id}") +async def check_if_friend_ignored(w_id: str): + """ + 妫�鏌ユ寚瀹歸_id鐨勮缁嗗拷鐣ョ姸鎬� + + Args: + w_id: 瑕佹鏌ョ殑w_id + + Returns: + 璇︾粏鐨勬鏌ョ粨鏋� + """ + try: + status_info = friend_ignore_service.get_ignore_status_info(w_id) + + return { + "success": True, + "data": status_info, + "message": f"w_id {w_id} 鐘舵�佹鏌ュ畬鎴�" + } + + except Exception as e: + logger.error(f"妫�鏌ュ拷鐣ョ姸鎬佸け璐�: {str(e)}") + raise HTTPException(status_code=500, detail=f"妫�鏌ュ拷鐣ョ姸鎬佸け璐�: {str(e)}") + + +@router.get("/whitelist") +async def get_whitelist(): + """ + 鑾峰彇褰撳墠鐨勭櫧鍚嶅崟鍒楄〃 + + Returns: + 鐧藉悕鍗曞垪琛� + """ + try: + whitelist = friend_ignore_service.get_whitelist() + + return { + "success": True, + "data": whitelist, + "count": len(whitelist), + "message": "鑾峰彇鐧藉悕鍗曟垚鍔�" + } + + except Exception as e: + logger.error(f"鑾峰彇鐧藉悕鍗曞け璐�: {str(e)}") + raise HTTPException(status_code=500, detail=f"鑾峰彇鐧藉悕鍗曞け璐�: {str(e)}") + + +@router.get("/config") +async def get_ignore_config(): + """ + 鑾峰彇濂藉弸蹇界暐鍔熻兘鐨勯厤缃俊鎭� + + Returns: + 閰嶇疆淇℃伅 + """ + try: + return { + "success": True, + "data": { + "ignore_enabled": settings.friend_ignore_enabled, + "whitelist": settings.friend_ignore_whitelist, + "whitelist_count": len(settings.friend_ignore_whitelist), + "ignore_list_count": friend_ignore_service.get_ignore_list_count() + }, + "message": "鑾峰彇閰嶇疆淇℃伅鎴愬姛" + } + + except Exception as e: + logger.error(f"鑾峰彇閰嶇疆淇℃伅澶辫触: {str(e)}") + raise HTTPException(status_code=500, detail=f"鑾峰彇閰嶇疆淇℃伅澶辫触: {str(e)}") diff --git a/app/services/contact_sync.py b/app/services/contact_sync.py index 42d4acd..a775333 100644 --- a/app/services/contact_sync.py +++ b/app/services/contact_sync.py @@ -8,6 +8,7 @@ from app.models.contact import Contact from app.models.database import get_db from app.services.ecloud_client import ecloud_client +from app.services.friend_ignore_service import friend_ignore_service class ContactSyncService: @@ -48,7 +49,10 @@ logger.info(f"鑾峰彇鍒板ソ鍙嬪垪琛�: wId={w_id}, count={len(friends)}") - # 4. 鎵归噺鑾峰彇鑱旂郴浜鸿缁嗕俊鎭� + # 4. 灏嗗ソ鍙媤_id娣诲姞鍒癛edis蹇界暐鍒楄〃 + friend_ignore_service.add_friends_to_ignore_list(friends) + + # 5. 鎵归噺鑾峰彇鑱旂郴浜鸿缁嗕俊鎭� return self._batch_sync_contacts(w_id, friends) except Exception as e: diff --git a/app/services/dify_client.py b/app/services/dify_client.py index f029c8e..e7ab10e 100644 --- a/app/services/dify_client.py +++ b/app/services/dify_client.py @@ -4,6 +4,7 @@ import requests import time +import json from typing import List, Dict, Optional, Any from loguru import logger from config import settings @@ -98,6 +99,315 @@ ) return None + def send_chat_message_stream( + self, + query: str, + user: str, + conversation_id: Optional[str] = None, + max_retries: int = None, + ) -> Optional[Dict[str, Any]]: + """ + 鍙戦�佸璇濇秷鎭紙娴佸紡妯″紡锛� + + Args: + query: 鐢ㄦ埛杈撳叆/鎻愰棶鍐呭 + user: 鐢ㄦ埛鏍囪瘑 + conversation_id: 浼氳瘽ID锛堝彲閫夛級 + max_retries: 鏈�澶ч噸璇曟鏁� + + Returns: + 瀹屾暣鐨勫搷搴旀暟鎹瓧鍏革紝澶辫触杩斿洖None + """ + if max_retries is None: + max_retries = settings.max_retry_count + + url = f"{self.base_url}/chat-messages" + payload = { + "query": query, + "response_mode": "streaming", # 浣跨敤娴佸紡妯″紡 + "user": user, + "inputs": {}, + } + + # 濡傛灉鏈変細璇滻D锛屾坊鍔犲埌璇锋眰涓� + if conversation_id: + payload["conversation_id"] = conversation_id + + retry_count = 0 + while retry_count <= max_retries: + try: + logger.info( + f"鍙戦�丏ify娴佸紡娑堟伅: user={user}, conversation_id={conversation_id}, retry={retry_count}" + ) + + response = self.session.post( + url, + json=payload, + timeout=settings.dify_streaming_timeout, + stream=True + ) + response.raise_for_status() + + # 澶勭悊娴佸紡鍝嶅簲 + result = self._process_stream_response(response, user) + + if result: + logger.info( + f"Dify娴佸紡娑堟伅鍙戦�佹垚鍔�: user={user}, conversation_id={result.get('conversation_id')}" + ) + return result + else: + logger.error(f"Dify娴佸紡鍝嶅簲澶勭悊澶辫触: user={user}") + + except requests.exceptions.Timeout: + logger.warning(f"Dify娴佸紡璇锋眰瓒呮椂: user={user}, retry={retry_count}") + except requests.exceptions.RequestException as e: + logger.error( + f"Dify娴佸紡缃戠粶閿欒: user={user}, retry={retry_count}, error={str(e)}" + ) + except Exception as e: + logger.error( + f"Dify娴佸紡璇锋眰寮傚父: user={user}, retry={retry_count}, error={str(e)}" + ) + + retry_count += 1 + if retry_count <= max_retries: + wait_time = settings.retry_delay * retry_count + logger.info(f"绛夊緟閲嶈瘯: user={user}, wait_time={wait_time}s") + time.sleep(wait_time) + + logger.error( + f"Dify娴佸紡娑堟伅鍙戦�佸け璐ワ紝宸茶揪鏈�澶ч噸璇曟鏁�: user={user}, max_retries={max_retries}" + ) + return None + + def _process_stream_response(self, response: requests.Response, user: str) -> Optional[Dict[str, Any]]: + """ + 澶勭悊娴佸紡鍝嶅簲 + + Args: + response: requests鍝嶅簲瀵硅薄 + user: 鐢ㄦ埛鏍囪瘑 + + Returns: + 瀹屾暣鐨勫搷搴旀暟鎹瓧鍏革紝澶辫触杩斿洖None + """ + try: + # 妫�鏌ュ搷搴斿ご + content_type = response.headers.get('content-type', '') + if 'text/event-stream' not in content_type: + logger.warning(f"鍝嶅簲涓嶆槸SSE鏍煎紡: user={user}, content_type={content_type}") + + complete_answer = "" + conversation_id = "" + task_id = "" + message_id = "" + created_at = None + metadata = None + usage = None + retriever_resources = None + message_ended = False # 鏍囪鏄惁鏀跺埌message_end浜嬩欢 + + logger.info(f"寮�濮嬪鐞嗘祦寮忓搷搴�: user={user}") + + # 娣诲姞瓒呮椂鍜岃璁℃暟鍣� + line_count = 0 + max_empty_lines = 50 # 鏈�澶ц繛缁┖琛屾暟锛岄槻姝㈡棤闄愬惊鐜� + + for line in response.iter_lines(decode_unicode=True): + line_count += 1 + + if not line: + # 绌鸿璁℃暟锛岄槻姝㈡棤闄愮瓑寰� + if line_count > max_empty_lines and not complete_answer: + logger.warning(f"娴佸紡鍝嶅簲杩囧绌鸿锛屽彲鑳借繛鎺ュ紓甯�: user={user}, line_count={line_count}") + break + continue + + # 璺宠繃闈炴暟鎹 + if not line.startswith("data: "): + continue + + # 鎻愬彇JSON鏁版嵁 + data_str = line[6:] # 绉婚櫎 "data: " 鍓嶇紑 + + if not data_str.strip(): + continue + + try: + data = json.loads(data_str) + event = data.get("event", "") + + if event == "message": + # 娑堟伅浜嬩欢 - 绱Н绛旀鍐呭 + answer_chunk = data.get("answer", "") + complete_answer += answer_chunk + + # 淇濆瓨鍩烘湰淇℃伅 + if not conversation_id: + conversation_id = data.get("conversation_id", "") + if not task_id: + task_id = data.get("task_id", "") + if not message_id: + message_id = data.get("id", "") + if created_at is None: + created_at = data.get("created_at") + + logger.debug(f"鏀跺埌娑堟伅鍧�: user={user}, chunk_length={len(answer_chunk)}") + + elif event == "message_end": + # 娑堟伅缁撴潫浜嬩欢 - 鑾峰彇鍏冩暟鎹� + metadata = data.get("metadata") + usage = data.get("usage") + retriever_resources = data.get("retriever_resources") + message_ended = True + + logger.info(f"娴佸紡鍝嶅簲瀹屾垚: user={user}, total_length={len(complete_answer)}") + break + + elif event == "message_file": + # 鏂囦欢浜嬩欢 - 璁板綍浣嗙户缁鐞� + logger.debug(f"鏀跺埌鏂囦欢浜嬩欢: user={user}, file_type={data.get('type')}") + continue + + elif event == "message_replace": + # 娑堟伅鏇挎崲浜嬩欢 - 鏇挎崲绛旀鍐呭 + replace_answer = data.get("answer", "") + if replace_answer: + complete_answer = replace_answer + logger.info(f"娑堟伅鍐呭琚浛鎹�: user={user}, new_length={len(complete_answer)}") + continue + + elif event in ["workflow_started", "node_started", "node_finished", "workflow_finished"]: + # 宸ヤ綔娴佺浉鍏充簨浠� - 璁板綍浣嗙户缁鐞� + logger.debug(f"鏀跺埌宸ヤ綔娴佷簨浠�: user={user}, event={event}") + continue + + elif event in ["tts_message", "tts_message_end"]: + # TTS闊抽浜嬩欢 - 璁板綍浣嗙户缁鐞� + logger.debug(f"鏀跺埌TTS浜嬩欢: user={user}, event={event}") + continue + + elif event in ["agent_thought", "agent_message"]: + # Agent鐩稿叧浜嬩欢 - 闇�瑕佺壒娈婂鐞� + logger.debug(f"鏀跺埌Agent浜嬩欢: user={user}, event={event}, data_keys={list(data.keys())}") + + # 浠巃gent浜嬩欢涓彁鍙朿onversation_id锛堝鏋滄湁鐨勮瘽锛� + if not conversation_id and data.get("conversation_id"): + conversation_id = data.get("conversation_id") + logger.info(f"浠嶢gent浜嬩欢鑾峰彇conversation_id: user={user}, conversation_id={conversation_id}") + + # 浠巃gent浜嬩欢涓彁鍙栧熀鏈俊鎭紙濡傛灉鏈夌殑璇濓級 + if not task_id and data.get("task_id"): + task_id = data.get("task_id") + logger.debug(f"浠嶢gent浜嬩欢鑾峰彇task_id: user={user}, task_id={task_id}") + if not message_id and data.get("id"): + message_id = data.get("id") + logger.debug(f"浠嶢gent浜嬩欢鑾峰彇message_id: user={user}, message_id={message_id}") + if created_at is None and data.get("created_at"): + created_at = data.get("created_at") + logger.debug(f"浠嶢gent浜嬩欢鑾峰彇created_at: user={user}, created_at={created_at}") + + # 妫�鏌gent_message鏄惁鍖呭惈answer鍐呭 + if event == "agent_message" and data.get("answer"): + agent_answer = data.get("answer", "") + complete_answer += agent_answer + logger.debug(f"浠嶢gent娑堟伅鑾峰彇鍐呭: user={user}, chunk_length={len(agent_answer)}") + + continue + + elif event == "error": + # 閿欒浜嬩欢 + error_msg = data.get("message", "鏈煡閿欒") + logger.error(f"娴佸紡鍝嶅簲閿欒: user={user}, error={error_msg}") + return None + + elif event == "ping": + # ping浜嬩欢 - 淇濇寔杩炴帴 + logger.debug(f"鏀跺埌ping浜嬩欢: user={user}") + continue + + else: + # 鏈煡浜嬩欢绫诲瀷 - 璁板綍浣嗙户缁鐞� + logger.debug(f"鏀跺埌鏈煡浜嬩欢: user={user}, event={event}") + continue + + except json.JSONDecodeError as e: + logger.warning(f"瑙f瀽娴佸紡鏁版嵁JSON澶辫触: user={user}, data={data_str}, error={str(e)}") + continue + + # 鏋勫缓瀹屾暣鍝嶅簲 + # 瀵逛簬Agent妯″紡锛屽彲鑳芥病鏈塩onversation_id锛屼絾鏈塼ask_id + # 鍦ㄨ繖绉嶆儏鍐典笅锛屾垜浠彲浠ヤ娇鐢╰ask_id浣滀负conversation_id鐨勬浛浠� + if conversation_id or (task_id and (complete_answer or message_ended)): + # 濡傛灉娌℃湁conversation_id浣嗘湁task_id锛屼娇鐢╰ask_id + final_conversation_id = conversation_id or task_id + + result = { + "event": "message", + "task_id": task_id, + "id": message_id, + "message_id": message_id, + "conversation_id": final_conversation_id, + "mode": "chat", + "answer": complete_answer, # 鍙兘涓虹┖瀛楃涓� + "created_at": created_at + } + + # 娣诲姞鍙�夊瓧娈� + if metadata: + result["metadata"] = metadata + if usage: + result["usage"] = usage + if retriever_resources: + result["retriever_resources"] = retriever_resources + + if complete_answer: + logger.info(f"娴佸紡鍝嶅簲澶勭悊鎴愬姛: user={user}, answer_length={len(complete_answer)}, conversation_id={final_conversation_id}, message_ended={message_ended}") + else: + logger.info(f"娴佸紡鍝嶅簲澶勭悊鎴愬姛(鏃犲唴瀹�): user={user}, conversation_id={final_conversation_id}, message_ended={message_ended}") + return result + else: + logger.error(f"娴佸紡鍝嶅簲涓嶅畬鏁�: user={user}, answer={bool(complete_answer)}, conversation_id={bool(conversation_id)}, task_id={bool(task_id)}") + # 璁板綍鏇村璋冭瘯淇℃伅 + logger.debug(f"璋冭瘯淇℃伅: task_id={task_id}, message_id={message_id}, created_at={created_at}, message_ended={message_ended}") + return None + + except Exception as e: + logger.error(f"澶勭悊娴佸紡鍝嶅簲寮傚父: user={user}, error={str(e)}") + return None + + def send_message( + self, + query: str, + user: str, + conversation_id: Optional[str] = None, + max_retries: int = None, + force_streaming: Optional[bool] = None, + ) -> Optional[Dict[str, Any]]: + """ + 鍙戦�佸璇濇秷鎭紙鏍规嵁閰嶇疆閫夋嫨妯″紡锛� + + Args: + query: 鐢ㄦ埛杈撳叆/鎻愰棶鍐呭 + user: 鐢ㄦ埛鏍囪瘑 + conversation_id: 浼氳瘽ID锛堝彲閫夛級 + max_retries: 鏈�澶ч噸璇曟鏁� + force_streaming: 寮哄埗浣跨敤娴佸紡妯″紡锛堝彲閫夛紝瑕嗙洊閰嶇疆锛� + + Returns: + 鍝嶅簲鏁版嵁瀛楀吀锛屽け璐ヨ繑鍥濶one + """ + # 纭畾浣跨敤鍝妯″紡 + use_streaming = force_streaming if force_streaming is not None else settings.dify_streaming_enabled + + if use_streaming: + logger.info(f"浣跨敤娴佸紡妯″紡鍙戦�佹秷鎭�: user={user}") + return self.send_chat_message_stream(query, user, conversation_id, max_retries) + else: + logger.info(f"浣跨敤闃诲妯″紡鍙戦�佹秷鎭�: user={user}") + return self.send_chat_message(query, user, conversation_id, max_retries) + def get_conversation_messages( self, conversation_id: str, user: str ) -> Optional[List[Dict[str, Any]]]: diff --git a/app/services/friend_ignore_service.py b/app/services/friend_ignore_service.py new file mode 100644 index 0000000..061c771 --- /dev/null +++ b/app/services/friend_ignore_service.py @@ -0,0 +1,259 @@ +""" +濂藉弸蹇界暐鍒楄〃绠$悊鏈嶅姟 +""" + +from typing import List, Set, Optional +from loguru import logger +from sqlalchemy.orm import Session +from app.services.redis_queue import redis_queue +from app.models.database import get_db +from app.models.contact import Contact +from config import settings + + +class FriendIgnoreService: + """濂藉弸蹇界暐鍒楄〃绠$悊鏈嶅姟""" + + def __init__(self): + self.ignore_list_key = "ecloud_ignore_friends" + + def _get_wid_by_nickname(self, nickname: str) -> Optional[str]: + """ + 鏍规嵁鏄电О鑾峰彇w_id + + Args: + nickname: 濂藉弸鏄电О + + Returns: + 瀵瑰簲鐨剋_id锛屽鏋滄湭鎵惧埌杩斿洖None + """ + try: + with next(get_db()) as db: + contact = db.query(Contact).filter(Contact.nick_name == nickname).first() + if contact: + return contact.wc_id + else: + logger.warning(f"鏈壘鍒版樀绉颁负 '{nickname}' 鐨勮仈绯讳汉") + return None + except Exception as e: + logger.error(f"鏍规嵁鏄电О鏌ユ壘w_id寮傚父: nickname={nickname}, error={str(e)}") + return None + + def _get_whitelist_wids(self) -> List[str]: + """ + 灏嗛厤缃腑鐨勬樀绉扮櫧鍚嶅崟杞崲涓簑_id鍒楄〃 + + Returns: + w_id鍒楄〃 + """ + wid_list = [] + for nickname in settings.friend_ignore_whitelist: + wid = self._get_wid_by_nickname(nickname) + if wid: + wid_list.append(wid) + logger.debug(f"鐧藉悕鍗曟樀绉� '{nickname}' 瀵瑰簲w_id: {wid}") + else: + logger.warning(f"鐧藉悕鍗曟樀绉� '{nickname}' 鏈壘鍒板搴旂殑鑱旂郴浜�") + return wid_list + + def add_friends_to_ignore_list(self, friends: List[str]) -> bool: + """ + 灏嗗ソ鍙媤_id娣诲姞鍒癛edis蹇界暐鍒楄〃 + + Args: + friends: 濂藉弸w_id鍒楄〃 + + Returns: + 娣诲姞鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse + """ + try: + if not friends: + logger.info("濂藉弸鍒楄〃涓虹┖锛屾棤闇�娣诲姞鍒板拷鐣ュ垪琛�") + return True + + # 娓呯┖鐜版湁鐨勫拷鐣ュ垪琛� + redis_queue.redis_client.delete(self.ignore_list_key) + + # 鎵归噺娣诲姞濂藉弸w_id鍒板拷鐣ュ垪琛� + redis_queue.redis_client.sadd(self.ignore_list_key, *friends) + + logger.info(f"宸插皢 {len(friends)} 涓ソ鍙嬫坊鍔犲埌蹇界暐鍒楄〃") + return True + + except Exception as e: + logger.error(f"娣诲姞濂藉弸鍒板拷鐣ュ垪琛ㄥ紓甯�: error={str(e)}") + return False + + def is_friend_ignored(self, w_id: str) -> bool: + """ + 妫�鏌ユ寚瀹歸_id鏄惁搴旇琚拷鐣� + + 閫昏緫锛� + 1. 濡傛灉濂藉弸蹇界暐鍔熻兘鏈惎鐢紝杩斿洖False锛堜笉蹇界暐锛� + 2. 濡傛灉w_id鍦ㄧ櫧鍚嶅崟涓紝杩斿洖False锛堜笉蹇界暐锛� + 3. 濡傛灉w_id鍦ㄥ拷鐣ュ垪琛ㄤ腑锛岃繑鍥濼rue锛堝拷鐣ワ級 + 4. 濡傛灉w_id涓嶅湪蹇界暐鍒楄〃涓紝杩斿洖False锛堜笉蹇界暐锛� + + Args: + w_id: 鐢ㄦ埛w_id + + Returns: + 濡傛灉搴旇琚拷鐣ヨ繑鍥濼rue锛屽惁鍒欒繑鍥濬alse + """ + try: + # 妫�鏌ュソ鍙嬪拷鐣ュ姛鑳芥槸鍚﹀惎鐢� + if not settings.friend_ignore_enabled: + logger.debug(f"濂藉弸蹇界暐鍔熻兘宸茬鐢紝涓嶅拷鐣ユ秷鎭�: w_id={w_id}") + return False + + # 妫�鏌ユ槸鍚﹀湪鐧藉悕鍗曚腑锛堥�氳繃鏄电О锛� + whitelist_wids = self._get_whitelist_wids() + if w_id in whitelist_wids: + logger.info(f"w_id鍦ㄧ櫧鍚嶅崟涓紝涓嶅拷鐣ユ秷鎭�: w_id={w_id}") + return False + + # 妫�鏌ユ槸鍚﹀湪蹇界暐鍒楄〃涓� + is_in_ignore_list = redis_queue.redis_client.sismember(self.ignore_list_key, w_id) + + if is_in_ignore_list: + logger.info(f"w_id鍦ㄥ拷鐣ュ垪琛ㄤ腑锛屽拷鐣ユ秷鎭�: w_id={w_id}") + + return is_in_ignore_list + + except Exception as e: + logger.error(f"妫�鏌ュ拷鐣ュ垪琛ㄥ紓甯�: w_id={w_id}, error={str(e)}") + return False + + def get_ignore_list(self) -> Set[str]: + """ + 鑾峰彇瀹屾暣鐨勫拷鐣ュ垪琛� + + Returns: + 蹇界暐鍒楄〃涓殑鎵�鏈墂_id闆嗗悎 + """ + try: + return redis_queue.redis_client.smembers(self.ignore_list_key) + except Exception as e: + logger.error(f"鑾峰彇蹇界暐鍒楄〃寮傚父: error={str(e)}") + return set() + + def remove_friend_from_ignore_list(self, w_id: str) -> bool: + """ + 浠庡拷鐣ュ垪琛ㄤ腑绉婚櫎鎸囧畾w_id + + Args: + w_id: 鐢ㄦ埛w_id + + Returns: + 绉婚櫎鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse + """ + try: + result = redis_queue.redis_client.srem(self.ignore_list_key, w_id) + if result: + logger.info(f"宸蹭粠蹇界暐鍒楄〃涓Щ闄�: w_id={w_id}") + else: + logger.info(f"w_id涓嶅湪蹇界暐鍒楄〃涓�: w_id={w_id}") + return True + except Exception as e: + logger.error(f"浠庡拷鐣ュ垪琛ㄧЩ闄_id寮傚父: w_id={w_id}, error={str(e)}") + return False + + def clear_ignore_list(self) -> bool: + """ + 娓呯┖蹇界暐鍒楄〃 + + Returns: + 娓呯┖鎴愬姛杩斿洖True锛屽け璐ヨ繑鍥濬alse + """ + try: + redis_queue.redis_client.delete(self.ignore_list_key) + logger.info("宸叉竻绌哄拷鐣ュ垪琛�") + return True + except Exception as e: + logger.error(f"娓呯┖蹇界暐鍒楄〃寮傚父: error={str(e)}") + return False + + def get_ignore_list_count(self) -> int: + """ + 鑾峰彇蹇界暐鍒楄〃涓殑濂藉弸鏁伴噺 + + Returns: + 蹇界暐鍒楄〃涓殑濂藉弸鏁伴噺 + """ + try: + return redis_queue.redis_client.scard(self.ignore_list_key) + except Exception as e: + logger.error(f"鑾峰彇蹇界暐鍒楄〃鏁伴噺寮傚父: error={str(e)}") + return 0 + + def get_whitelist(self) -> List[str]: + """ + 鑾峰彇褰撳墠鐨勭櫧鍚嶅崟鍒楄〃 + + Returns: + 鐧藉悕鍗曚腑鐨剋_id鍒楄〃 + """ + return settings.friend_ignore_whitelist.copy() + + def is_whitelist_enabled(self) -> bool: + """ + 妫�鏌ョ櫧鍚嶅崟鍔熻兘鏄惁鍚敤 + + Returns: + 濡傛灉鍚敤杩斿洖True锛屽惁鍒欒繑鍥濬alse + """ + return settings.friend_ignore_enabled + + def get_ignore_status_info(self, w_id: str) -> dict: + """ + 鑾峰彇鎸囧畾w_id鐨勮缁嗗拷鐣ョ姸鎬佷俊鎭� + + Args: + w_id: 鐢ㄦ埛w_id + + Returns: + 鍖呭惈璇︾粏鐘舵�佷俊鎭殑瀛楀吀 + """ + try: + # 鑾峰彇鐧藉悕鍗晈_id鍒楄〃 + whitelist_wids = self._get_whitelist_wids() + + info = { + "w_id": w_id, + "ignore_enabled": settings.friend_ignore_enabled, + "in_whitelist": w_id in whitelist_wids, + "in_ignore_list": False, + "final_ignored": False, + "reason": "", + "whitelist_nicknames": settings.friend_ignore_whitelist + } + + if not settings.friend_ignore_enabled: + info["reason"] = "濂藉弸蹇界暐鍔熻兘宸茬鐢�" + return info + + if w_id in whitelist_wids: + info["reason"] = "鍦ㄧ櫧鍚嶅崟涓紝涓嶄細琚拷鐣�" + return info + + info["in_ignore_list"] = redis_queue.redis_client.sismember(self.ignore_list_key, w_id) + + if info["in_ignore_list"]: + info["final_ignored"] = True + info["reason"] = "鍦ㄥ拷鐣ュ垪琛ㄤ腑锛屼細琚拷鐣�" + else: + info["reason"] = "涓嶅湪蹇界暐鍒楄〃涓紝涓嶄細琚拷鐣�" + + return info + + except Exception as e: + logger.error(f"鑾峰彇蹇界暐鐘舵�佷俊鎭紓甯�: w_id={w_id}, error={str(e)}") + return { + "w_id": w_id, + "error": str(e), + "final_ignored": False + } + + +# 鍏ㄥ眬濂藉弸蹇界暐鏈嶅姟瀹炰緥 +friend_ignore_service = FriendIgnoreService() diff --git a/app/services/message_processor.py b/app/services/message_processor.py index 12aaea2..e70555a 100644 --- a/app/services/message_processor.py +++ b/app/services/message_processor.py @@ -16,6 +16,7 @@ from app.services.redis_queue import redis_queue 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 config import settings @@ -99,6 +100,12 @@ or not data.get("content") ): 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}") return False return True @@ -210,8 +217,8 @@ # 3.2 鑾峰彇鐢ㄦ埛鍦ㄥ綋鍓嶇兢缁勭殑conversation_id conversation_id = redis_queue.get_conversation_id(from_user, from_group) - # 璋冪敤Dify鎺ュ彛鍙戦�佹秷鎭� - dify_response = dify_client.send_chat_message( + # 璋冪敤Dify鎺ュ彛鍙戦�佹秷鎭紙鏍规嵁閰嶇疆閫夋嫨妯″紡锛� + dify_response = dify_client.send_message( query=content, user=from_user, conversation_id=conversation_id ) diff --git a/config.example.json b/config.example.json deleted file mode 100644 index b2df84d..0000000 --- a/config.example.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "database": { - "url": "mysql+pymysql://username:password@host:port/database_name" - }, - "redis": { - "url": "redis://localhost:6379/0" - }, - "ecloud": { - "base_url": "http://your-ecloud-server:port", - "authorization": "your_ecloud_authorization_token", - "w_id": "your_ecloud_w_id" - }, - "dify": { - "base_url": "https://api.dify.ai/v1", - "api_key": "your_dify_api_key" - }, - "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"] - } -} diff --git a/config.production.json b/config.production.json deleted file mode 100644 index efbd202..0000000 --- a/config.production.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "database": { - "url": "mysql+pymysql://root:password@localhost:3306/ecloud_dify" - }, - "redis": { - "url": "redis://localhost:6379/0" - }, - "ecloud": { - "base_url": "http://125.122.152.142:9899", - "authorization": "your_ecloud_authorization_token" - }, - "dify": { - "base_url": "https://api.dify.ai/v1", - "api_key": "your_dify_api_key" - }, - "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 - } -} diff --git a/config.py b/config.py index d7919aa..c9792f3 100644 --- a/config.py +++ b/config.py @@ -16,91 +16,62 @@ def _load_config(self): """浠嶫SON鏂囦欢鍔犺浇閰嶇疆""" + if not os.path.exists(self.config_file): + raise FileNotFoundError(f"閰嶇疆鏂囦欢 {self.config_file} 涓嶅瓨鍦�") + try: - if os.path.exists(self.config_file): - with open(self.config_file, 'r', encoding='utf-8') as f: - config_data = json.load(f) - self._set_config_from_dict(config_data) - else: - # 濡傛灉閰嶇疆鏂囦欢涓嶅瓨鍦紝浣跨敤榛樿鍊� - self._set_default_config() + with open(self.config_file, 'r', encoding='utf-8') as f: + config_data = json.load(f) + self._set_config_from_dict(config_data) except Exception as e: - print(f"鍔犺浇閰嶇疆鏂囦欢澶辫触: {e}") - self._set_default_config() + raise Exception(f"鍔犺浇閰嶇疆鏂囦欢澶辫触: {e}") def _set_config_from_dict(self, config_data: dict): """浠庡瓧鍏歌缃厤缃�""" # 鏁版嵁搴撻厤缃� - self.database_url = config_data.get("database", {}).get("url", "mysql+pymysql://root:TAI%402019%23Zjun@120.24.39.179:3306/ecloud_dify") + self.database_url = config_data["database"]["url"] # Redis閰嶇疆 - self.redis_url = config_data.get("redis", {}).get("url", "redis://localhost:6379/0") + self.redis_url = config_data["redis"]["url"] # E浜戠瀹堕厤缃� - ecloud_config = config_data.get("ecloud", {}) - self.ecloud_base_url = ecloud_config.get("base_url", "http://125.122.152.142:9899") - self.ecloud_authorization = ecloud_config.get("authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMzYxMTQ1MjE3NSIsInBhc3N3b3JkIjoiJDJhJDEwJEU3Ry5LOEJzekphM2JGQlh0SG8vOXVrUk1NalVweGVVemguUDRnMkJBdHN2YXpBb0JIQWJpIn0.Gd2vbeJjL5pUGFhUngWPLkDTLhD3GUaEPXOkdoTf4KRh9o2FtST1OZJxmZuGdUy7WIYlIPVueoVyIu5iHOyi8A") - self.ecloud_w_id = ecloud_config.get("w_id", "") + ecloud_config = config_data["ecloud"] + self.ecloud_base_url = ecloud_config["base_url"] + self.ecloud_authorization = ecloud_config["authorization"] + self.ecloud_w_id = ecloud_config["w_id"] # DifyAI閰嶇疆 - dify_config = config_data.get("dify", {}) - self.dify_base_url = dify_config.get("base_url", "https://api.dify.ai/v1") - self.dify_api_key = dify_config.get("api_key", "app-OMnBr7zsf5UTV83Ey8QcSErA") + dify_config = config_data["dify"] + self.dify_base_url = dify_config["base_url"] + self.dify_api_key = dify_config["api_key"] + self.dify_streaming_enabled = dify_config["streaming_enabled"] + self.dify_streaming_timeout = dify_config["streaming_timeout"] # 鏈嶅姟閰嶇疆 - server_config = config_data.get("server", {}) - self.server_host = server_config.get("host", "0.0.0.0") - self.server_port = server_config.get("port", 7979) - self.debug = server_config.get("debug", True) + server_config = config_data["server"] + self.server_host = server_config["host"] + self.server_port = server_config["port"] + self.debug = server_config["debug"] # 鏃ュ織閰嶇疆 - logging_config = config_data.get("logging", {}) - self.log_level = logging_config.get("level", "INFO") - self.log_file = logging_config.get("file", "logs/app.log") + logging_config = config_data["logging"] + self.log_level = logging_config["level"] + self.log_file = logging_config["file"] # 娑堟伅澶勭悊閰嶇疆 - msg_config = config_data.get("message_processing", {}) - self.max_retry_count = msg_config.get("max_retry_count", 3) - self.retry_delay = msg_config.get("retry_delay", 5) - self.queue_timeout = msg_config.get("queue_timeout", 300) + msg_config = config_data["message_processing"] + self.max_retry_count = msg_config["max_retry_count"] + self.retry_delay = msg_config["retry_delay"] + self.queue_timeout = msg_config["queue_timeout"] # 瀹㈡湇閰嶇疆 - customer_service_config = config_data.get("customer_service", {}) - self.customer_service_names = customer_service_config.get("names", ["瀹㈡湇1", "瀹㈡湇2"]) + customer_service_config = config_data["customer_service"] + self.customer_service_names = customer_service_config["names"] - def _set_default_config(self): - """璁剧疆榛樿閰嶇疆""" - # 鏁版嵁搴撻厤缃� - self.database_url = "mysql+pymysql://root:TAI%402019%23Zjun@120.24.39.179:3306/ecloud_dify" - - # Redis閰嶇疆 - self.redis_url = "redis://localhost:6379/0" - - # E浜戠瀹堕厤缃� - self.ecloud_base_url = "http://125.122.152.142:9899" - self.ecloud_authorization = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMzYxMTQ1MjE3NSIsInBhc3N3b3JkIjoiJDJhJDEwJEU3Ry5LOEJzekphM2JGQlh0SG8vOXVrUk1NalVweGVVemguUDRnMkJBdHN2YXpBb0JIQWJpIn0.Gd2vbeJjL5pUGFhUngWPLkDTLhD3GUaEPXOkdoTf4KRh9o2FtST1OZJxmZuGdUy7WIYlIPVueoVyIu5iHOyi8A" - self.ecloud_w_id = "" - - # DifyAI閰嶇疆 - self.dify_base_url = "https://api.dify.ai/v1" - self.dify_api_key = "app-OMnBr7zsf5UTV83Ey8QcSErA" - - # 鏈嶅姟閰嶇疆 - self.server_host = "0.0.0.0" - self.server_port = 7979 - self.debug = True - - # 鏃ュ織閰嶇疆 - self.log_level = "INFO" - self.log_file = "logs/app.log" - - # 娑堟伅澶勭悊閰嶇疆 - self.max_retry_count = 3 - self.retry_delay = 5 - self.queue_timeout = 300 - - # 瀹㈡湇閰嶇疆 - self.customer_service_names = ["瀹㈡湇1", "瀹㈡湇2"] + # 濂藉弸蹇界暐閰嶇疆 + friend_ignore_config = config_data["friend_ignore"] + self.friend_ignore_enabled = friend_ignore_config["enabled"] + self.friend_ignore_whitelist = friend_ignore_config["whitelist"] # 鍏ㄥ眬閰嶇疆瀹炰緥 diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 540f643..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,51 +0,0 @@ -version: '3.8' - -services: - # MySQL鏁版嵁搴� - mysql: - image: mysql:8.0 - container_name: ecloud_mysql - environment: - MYSQL_ROOT_PASSWORD: password123 - MYSQL_DATABASE: ecloud_dify - MYSQL_USER: ecloud - MYSQL_PASSWORD: ecloud123 - ports: - - "3306:3306" - volumes: - - mysql_data:/var/lib/mysql - - ./init.sql:/docker-entrypoint-initdb.d/init.sql - restart: unless-stopped - command: --default-authentication-plugin=mysql_native_password - - # Redis缂撳瓨 - redis: - image: redis:7-alpine - container_name: ecloud_redis - ports: - - "6379:6379" - volumes: - - redis_data:/data - restart: unless-stopped - command: redis-server --appendonly yes - - # 搴旂敤鏈嶅姟 - app: - build: . - container_name: ecloud_app - ports: - - "8000:8000" - environment: - - DATABASE_URL=mysql+pymysql://ecloud:ecloud123@mysql:3306/ecloud_dify - - REDIS_URL=redis://redis:6379/0 - volumes: - - ./logs:/app/logs - - ./.env:/app/.env - depends_on: - - mysql - - redis - restart: unless-stopped - -volumes: - mysql_data: - redis_data: diff --git a/install_service.bat b/install_service.bat deleted file mode 100644 index 4c0b14f..0000000 --- a/install_service.bat +++ /dev/null @@ -1,102 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 Windows鏈嶅姟瀹夎鑴氭湰 -echo ======================================== -echo. - -:: 妫�鏌ョ鐞嗗憳鏉冮檺 -net session >nul 2>&1 -if %errorLevel% == 0 ( - echo 妫�娴嬪埌绠$悊鍛樻潈闄愶紝缁х画瀹夎... -) else ( - echo 閿欒锛氶渶瑕佺鐞嗗憳鏉冮檺鎵嶈兘瀹夎Windows鏈嶅姟 - echo 璇峰彸閿偣鍑绘鑴氭湰锛岄�夋嫨"浠ョ鐞嗗憳韬唤杩愯" - pause - exit /b 1 -) - -:: 鑾峰彇褰撳墠鐩綍 -set "CURRENT_DIR=%~dp0" -set "SERVICE_NAME=ECloudDifyService" -set "SERVICE_DISPLAY_NAME=E浜戠瀹�-DifyAI瀵规帴鏈嶅姟" -set "SERVICE_DESCRIPTION=灏咵浜戠瀹舵秷鎭浆鍙戝埌DifyAI骞惰繑鍥濧I鍥炵瓟鐨勬湇鍔�" -set "EXE_PATH=%CURRENT_DIR%ecloud_dify.exe" - -echo 褰撳墠鐩綍: %CURRENT_DIR% -echo 鏈嶅姟鍚嶇О: %SERVICE_NAME% -echo 鍙墽琛屾枃浠�: %EXE_PATH% -echo. - -:: 妫�鏌xe鏂囦欢鏄惁瀛樺湪 -if not exist "%EXE_PATH%" ( - echo 閿欒锛氭壘涓嶅埌鍙墽琛屾枃浠� %EXE_PATH% - echo 璇风‘淇濆凡缁忓畬鎴愰」鐩墦鍖咃紝骞朵笖ecloud_dify.exe鏂囦欢瀛樺湪 - pause - exit /b 1 -) - -:: 鍋滄骞跺垹闄ゅ凡瀛樺湪鐨勬湇鍔� -echo 妫�鏌ユ槸鍚﹀瓨鍦ㄥ悓鍚嶆湇鍔�... -sc query "%SERVICE_NAME%" >nul 2>&1 -if %errorLevel% == 0 ( - echo 鍙戠幇宸插瓨鍦ㄧ殑鏈嶅姟锛屾鍦ㄥ仠姝㈠苟鍒犻櫎... - sc stop "%SERVICE_NAME%" >nul 2>&1 - timeout /t 3 /nobreak >nul - sc delete "%SERVICE_NAME%" >nul 2>&1 - if %errorLevel% == 0 ( - echo 宸插垹闄ゆ棫鏈嶅姟 - ) else ( - echo 璀﹀憡锛氬垹闄ゆ棫鏈嶅姟澶辫触锛岀户缁畨瑁呮柊鏈嶅姟 - ) - echo. -) - -:: 鍒涘缓Windows鏈嶅姟 -echo 姝e湪鍒涘缓Windows鏈嶅姟... -sc create "%SERVICE_NAME%" binPath= "\"%EXE_PATH%\"" DisplayName= "%SERVICE_DISPLAY_NAME%" start= auto -if %errorLevel% == 0 ( - echo 鏈嶅姟鍒涘缓鎴愬姛 -) else ( - echo 閿欒锛氭湇鍔″垱寤哄け璐� - pause - exit /b 1 -) - -:: 璁剧疆鏈嶅姟鎻忚堪 -sc description "%SERVICE_NAME%" "%SERVICE_DESCRIPTION%" - -:: 璁剧疆鏈嶅姟鎭㈠閫夐」锛堝け璐ユ椂鑷姩閲嶅惎锛� -sc failure "%SERVICE_NAME%" reset= 86400 actions= restart/5000/restart/10000/restart/30000 - -echo. -echo 鏈嶅姟瀹夎瀹屾垚锛� -echo. -echo 鍙敤鐨勬搷浣滐細 -echo 1. 鍚姩鏈嶅姟: sc start %SERVICE_NAME% -echo 2. 鍋滄鏈嶅姟: sc stop %SERVICE_NAME% -echo 3. 鏌ョ湅鏈嶅姟鐘舵��: sc query %SERVICE_NAME% -echo 4. 鍒犻櫎鏈嶅姟: sc delete %SERVICE_NAME% -echo. -echo 鎴栬�呬娇鐢╓indows鏈嶅姟绠$悊鍣� (services.msc) 杩涜绠$悊 -echo. - -:: 璇㈤棶鏄惁绔嬪嵆鍚姩鏈嶅姟 -set /p START_NOW="鏄惁绔嬪嵆鍚姩鏈嶅姟锛�(Y/N): " -if /i "%START_NOW%"=="Y" ( - echo 姝e湪鍚姩鏈嶅姟... - sc start "%SERVICE_NAME%" - if %errorLevel% == 0 ( - echo 鏈嶅姟鍚姩鎴愬姛锛� - echo 鏈嶅姟灏嗗湪绔彛 7979 涓婅繍琛� - echo 鍙互閫氳繃 http://localhost:7979 璁块棶鏈嶅姟 - ) else ( - echo 鏈嶅姟鍚姩澶辫触锛岃妫�鏌ラ厤缃枃浠跺拰鏃ュ織 - ) -) else ( - echo 鏈嶅姟宸插畨瑁呬絾鏈惎鍔� - echo 鍙互绋嶅悗閫氳繃 sc start %SERVICE_NAME% 鍚姩鏈嶅姟 -) - -echo. -pause diff --git a/logs/app.log b/logs/app.log index fec3d52..d8da35c 100644 --- a/logs/app.log +++ b/logs/app.log @@ -1,9 +1,4 @@ -2025-07-23 14:46:37 | INFO | __main__:<module>:105 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 15:03:24 | INFO | __main__:<module>:105 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 16:48:21 | INFO | __main__:<module>:121 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 16:50:14 | INFO | __main__:<module>:121 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 16:55:09 | INFO | __main__:<module>:121 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 16:56:49 | INFO | __main__:<module>:121 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 17:19:58 | INFO | __main__:<module>:121 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 17:43:16 | INFO | __main__:<module>:105 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 -2025-07-23 17:44:16 | INFO | __main__:<module>:105 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 +2025-07-28 09:54:56 | INFO | __main__:<module>:122 - 鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 +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瀵规帴鏈嶅姟 diff --git a/main.py b/main.py index 978923a..cba621f 100644 --- a/main.py +++ b/main.py @@ -10,8 +10,10 @@ import time from config import settings from app.api.callback import router as callback_router +from app.api.friend_ignore import router as friend_ignore_router from app.models.database import create_tables from app.workers.message_worker import message_worker +from app.services.contact_sync import contact_sync_service @asynccontextmanager @@ -33,6 +35,20 @@ logger.info("娑堟伅宸ヤ綔杩涚▼鍚姩鎴愬姛") except Exception as e: logger.error(f"娑堟伅宸ヤ綔杩涚▼鍚姩澶辫触: {str(e)}") + + # 鍚屾鑱旂郴浜轰俊鎭苟寤虹珛濂藉弸蹇界暐鍒楄〃 + try: + if settings.ecloud_w_id: + logger.info("寮�濮嬪悓姝ヨ仈绯讳汉淇℃伅...") + success = contact_sync_service.sync_contacts_on_startup(settings.ecloud_w_id) + if success: + logger.info("鑱旂郴浜哄悓姝ュ畬鎴愶紝濂藉弸蹇界暐鍒楄〃宸插缓绔�") + else: + logger.warning("鑱旂郴浜哄悓姝ュけ璐�") + else: + logger.warning("鏈厤缃甧cloud_w_id锛岃烦杩囪仈绯讳汉鍚屾") + except Exception as e: + logger.error(f"鑱旂郴浜哄悓姝ュ紓甯�: {str(e)}") logger.info("搴旂敤鍚姩瀹屾垚") @@ -70,6 +86,7 @@ # 娉ㄥ唽璺敱 app.include_router(callback_router, prefix="/api/v1", tags=["鍥炶皟鎺ュ彛"]) +app.include_router(friend_ignore_router, prefix="/api/v1", tags=["濂藉弸蹇界暐绠$悊"]) @app.get("/") diff --git a/start.bat b/start.bat deleted file mode 100644 index cc26592..0000000 --- a/start.bat +++ /dev/null @@ -1,42 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 鍚姩鑴氭湰 -echo ======================================== -echo. - -:: 鑾峰彇褰撳墠鐩綍 -set "CURRENT_DIR=%~dp0" -set "EXE_PATH=%CURRENT_DIR%ecloud_dify.exe" - -echo 褰撳墠鐩綍: %CURRENT_DIR% -echo 鍙墽琛屾枃浠�: %EXE_PATH% -echo. - -:: 妫�鏌xe鏂囦欢鏄惁瀛樺湪 -if not exist "%EXE_PATH%" ( - echo 閿欒锛氭壘涓嶅埌鍙墽琛屾枃浠� %EXE_PATH% - echo 璇风‘淇濆凡缁忓畬鎴愰」鐩墦鍖咃紝骞朵笖ecloud_dify.exe鏂囦欢瀛樺湪 - pause - exit /b 1 -) - -:: 妫�鏌ラ厤缃枃浠� -if not exist "%CURRENT_DIR%config.json" ( - echo 璀﹀憡锛氭湭鎵惧埌config.json閰嶇疆鏂囦欢 - echo 绋嬪簭灏嗕娇鐢ㄩ粯璁ら厤缃垨config.production.json妯℃澘 - echo. -) - -echo 姝e湪鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟... -echo 鏈嶅姟灏嗗湪绔彛 7979 涓婅繍琛� -echo 鍙互閫氳繃 http://localhost:7979 璁块棶鏈嶅姟 -echo 鎸� Ctrl+C 鍋滄鏈嶅姟 -echo. - -:: 鍚姩鏈嶅姟 -"%EXE_PATH%" - -echo. -echo 鏈嶅姟宸插仠姝� -pause diff --git a/start.sh b/start.sh deleted file mode 100644 index 9cf30c8..0000000 --- a/start.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash - -# E浜戠瀹�-DifyAI瀵规帴鏈嶅姟鍚姩鑴氭湰 - -echo "姝e湪鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟..." - -# 妫�鏌ython鐗堟湰 -python_version=$(python3 --version 2>&1 | grep -oP '\d+\.\d+') -if [[ $(echo "$python_version >= 3.11" | bc -l) -eq 0 ]]; then - echo "閿欒: 闇�瑕丳ython 3.11鎴栨洿楂樼増鏈紝褰撳墠鐗堟湰: $python_version" - exit 1 -fi - -# 鍒涘缓铏氭嫙鐜锛堝鏋滀笉瀛樺湪锛� -if [ ! -d "venv" ]; then - echo "鍒涘缓Python铏氭嫙鐜..." - python3 -m venv venv -fi - -# 婵�娲昏櫄鎷熺幆澧� -echo "婵�娲昏櫄鎷熺幆澧�..." -source venv/bin/activate - -# 瀹夎渚濊禆 -echo "瀹夎Python渚濊禆..." -pip install -r requirements.txt - -# 妫�鏌ョ幆澧冨彉閲忔枃浠� -if [ ! -f ".env" ]; then - echo "璀﹀憡: .env鏂囦欢涓嶅瓨鍦紝璇峰鍒�.env.example骞堕厤缃浉鍏冲弬鏁�" - cp .env.example .env - echo "宸插垱寤�.env鏂囦欢锛岃缂栬緫閰嶇疆鍚庨噸鏂拌繍琛�" - exit 1 -fi - -# 鍒涘缓鏃ュ織鐩綍 -mkdir -p logs - -# 妫�鏌ユ暟鎹簱杩炴帴 -echo "妫�鏌ユ暟鎹簱杩炴帴..." -python -c " -from app.utils.database_init import check_database_health -if not check_database_health(): - print('鏁版嵁搴撹繛鎺ュけ璐ワ紝璇锋鏌ラ厤缃�') - exit(1) -print('鏁版嵁搴撹繛鎺ユ甯�') -" - -if [ $? -ne 0 ]; then - echo "鏁版嵁搴撹繛鎺ュけ璐ワ紝璇锋鏌ラ厤缃�" - exit 1 -fi - -# 鍒濆鍖栨暟鎹簱 -echo "鍒濆鍖栨暟鎹簱..." -python app/utils/database_init.py - -# 鍚姩鏈嶅姟 -echo "鍚姩鏈嶅姟..." -python main.py diff --git a/startup.py b/startup.py deleted file mode 100644 index dad9b0e..0000000 --- a/startup.py +++ /dev/null @@ -1,139 +0,0 @@ -""" -鍚姩鑴氭湰 - 澶勭悊閰嶇疆鏂囦欢鍜岀洰褰曞垵濮嬪寲 -""" - -import os -import sys -import json -import shutil -from pathlib import Path - - -def get_exe_dir(): - """鑾峰彇exe鏂囦欢鎵�鍦ㄧ洰褰�""" - if getattr(sys, 'frozen', False): - # 濡傛灉鏄墦鍖呭悗鐨別xe鏂囦欢 - return os.path.dirname(sys.executable) - else: - # 濡傛灉鏄紑鍙戠幆澧� - return os.path.dirname(os.path.abspath(__file__)) - - -def ensure_directories(): - """纭繚蹇呰鐨勭洰褰曞瓨鍦�""" - exe_dir = get_exe_dir() - - # 鍒涘缓鏃ュ織鐩綍 - logs_dir = os.path.join(exe_dir, 'logs') - if not os.path.exists(logs_dir): - os.makedirs(logs_dir) - print(f"鍒涘缓鏃ュ織鐩綍: {logs_dir}") - - return exe_dir - - -def ensure_config_file(): - """纭繚閰嶇疆鏂囦欢瀛樺湪""" - exe_dir = get_exe_dir() - config_file = os.path.join(exe_dir, 'config.json') - - if not os.path.exists(config_file): - # 濡傛灉config.json涓嶅瓨鍦紝灏濊瘯澶嶅埗鐢熶骇閰嶇疆妯℃澘 - production_config = os.path.join(exe_dir, 'config.production.json') - example_config = os.path.join(exe_dir, 'config.example.json') - - if os.path.exists(production_config): - shutil.copy2(production_config, config_file) - print(f"澶嶅埗鐢熶骇閰嶇疆鏂囦欢: {production_config} -> {config_file}") - elif os.path.exists(example_config): - shutil.copy2(example_config, config_file) - print(f"澶嶅埗绀轰緥閰嶇疆鏂囦欢: {example_config} -> {config_file}") - else: - # 鍒涘缓榛樿閰嶇疆鏂囦欢 - default_config = { - "database": { - "url": "mysql+pymysql://root:password@localhost:3306/ecloud_dify" - }, - "redis": { - "url": "redis://localhost:6379/0" - }, - "ecloud": { - "base_url": "http://125.122.152.142:9899", - "authorization": "your_ecloud_authorization_token", - "w_id": "your_ecloud_w_id" - }, - "dify": { - "base_url": "https://api.dify.ai/v1", - "api_key": "your_dify_api_key" - }, - "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"] - } - } - - with open(config_file, 'w', encoding='utf-8') as f: - json.dump(default_config, f, indent=2, ensure_ascii=False) - print(f"鍒涘缓榛樿閰嶇疆鏂囦欢: {config_file}") - - return config_file - - -def setup_environment(): - """璁剧疆杩愯鐜""" - exe_dir = ensure_directories() - config_file = ensure_config_file() - - # 璁剧疆宸ヤ綔鐩綍涓篹xe鎵�鍦ㄧ洰褰� - os.chdir(exe_dir) - print(f"璁剧疆宸ヤ綔鐩綍: {exe_dir}") - - return exe_dir, config_file - - -if __name__ == "__main__": - setup_environment() - - # 瀵煎叆骞跺惎鍔ㄤ富搴旂敤 - try: - from main import app - import uvicorn - from config import settings - from loguru import logger - - # 閰嶇疆鏃ュ織 - logger.add( - settings.log_file, - rotation="1 day", - retention="7 days", - level=settings.log_level, - format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} - {message}", - ) - - logger.info("鍚姩E浜戠瀹�-DifyAI瀵规帴鏈嶅姟") - - # 鍚姩鏈嶅姟 - uvicorn.run( - app, - host=settings.server_host, - port=settings.server_port, - log_level=settings.log_level.lower(), - ) - - except Exception as e: - print(f"鍚姩澶辫触: {e}") - input("鎸変换鎰忛敭閫�鍑�...") - sys.exit(1) diff --git a/test_integration.py b/test_integration.py deleted file mode 100644 index 433d12a..0000000 --- a/test_integration.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python3 -""" -闆嗘垚娴嬭瘯鑴氭湰 -鐢ㄤ簬娴嬭瘯E浜戠瀹�-DifyAI瀵规帴鏈嶅姟鐨勫熀鏈姛鑳� -""" -import requests -import time -from datetime import datetime - - -def test_service_health(): - """娴嬭瘯鏈嶅姟鍋ュ悍鐘舵��""" - try: - response = requests.get("http://localhost:8000/api/v1/health", timeout=5) - if response.status_code == 200: - print("鉁� 鏈嶅姟鍋ュ悍妫�鏌ラ�氳繃") - return True - else: - print(f"鉂� 鏈嶅姟鍋ュ悍妫�鏌ュけ璐�: {response.status_code}") - return False - except Exception as e: - print(f"鉂� 鏈嶅姟杩炴帴澶辫触: {str(e)}") - return False - - -def test_callback_api(): - """娴嬭瘯鍥炶皟API""" - # 妯℃嫙E浜戠瀹跺洖璋冩暟鎹� - callback_data = { - "account": "17200000000", - "messageType": "80001", - "wcId": "wxid_test_user", - "data": { - "content": "浣犲ソ锛岃繖鏄竴鏉℃祴璇曟秷鎭�", - "fromGroup": "test_group@chatroom", - "fromUser": "wxid_test_user_123", - "memberCount": 5, - "msgId": int(time.time()), - "newMsgId": int(time.time() * 1000), - "self": False, - "timestamp": int(time.time()), - "toUser": "wxid_bot", - "wId": "test-instance-id", - }, - } - - try: - response = requests.post( - "http://localhost:8000/api/v1/callback", json=callback_data, timeout=10 - ) - - if response.status_code == 200: - result = response.json() - if result.get("success"): - print("鉁� 鍥炶皟API娴嬭瘯閫氳繃") - return True - else: - print(f"鉂� 鍥炶皟API澶勭悊澶辫触: {result.get('message')}") - return False - else: - print(f"鉂� 鍥炶皟API璇锋眰澶辫触: {response.status_code}") - return False - - except Exception as e: - print(f"鉂� 鍥炶皟API娴嬭瘯寮傚父: {str(e)}") - return False - - -def test_invalid_message(): - """娴嬭瘯鏃犳晥娑堟伅澶勭悊""" - # 娴嬭瘯闈炵兢鑱婃秷鎭� - invalid_data = { - "account": "17200000000", - "messageType": "80002", # 闈炵兢鑱婃秷鎭� - "wcId": "wxid_test_user", - "data": { - "content": "杩欐槸涓�鏉$鑱婃秷鎭�", - "fromUser": "wxid_test_user_123", - "self": False, - }, - } - - try: - response = requests.post( - "http://localhost:8000/api/v1/callback", json=invalid_data, timeout=10 - ) - - if response.status_code == 200: - result = response.json() - if not result.get("success"): - print("鉁� 鏃犳晥娑堟伅杩囨护娴嬭瘯閫氳繃") - return True - else: - print("鉂� 鏃犳晥娑堟伅鏈姝g‘杩囨护") - return False - else: - print(f"鉂� 鏃犳晥娑堟伅娴嬭瘯璇锋眰澶辫触: {response.status_code}") - return False - - except Exception as e: - print(f"鉂� 鏃犳晥娑堟伅娴嬭瘯寮傚父: {str(e)}") - return False - - -def test_self_message(): - """娴嬭瘯鑷繁鍙戦�佺殑娑堟伅杩囨护""" - self_message_data = { - "account": "17200000000", - "messageType": "80001", - "wcId": "wxid_test_user", - "data": { - "content": "杩欐槸鎴戣嚜宸卞彂閫佺殑娑堟伅", - "fromGroup": "test_group@chatroom", - "fromUser": "wxid_test_user_123", - "self": True, # 鑷繁鍙戦�佺殑娑堟伅 - "timestamp": int(time.time()), - }, - } - - try: - response = requests.post( - "http://localhost:8000/api/v1/callback", json=self_message_data, timeout=10 - ) - - if response.status_code == 200: - result = response.json() - if not result.get("success"): - print("鉁� 鑷彂娑堟伅杩囨护娴嬭瘯閫氳繃") - return True - else: - print("鉂� 鑷彂娑堟伅鏈姝g‘杩囨护") - return False - else: - print(f"鉂� 鑷彂娑堟伅娴嬭瘯璇锋眰澶辫触: {response.status_code}") - return False - - except Exception as e: - print(f"鉂� 鑷彂娑堟伅娴嬭瘯寮傚父: {str(e)}") - return False - - -def main(): - """涓绘祴璇曞嚱鏁�""" - print("=" * 50) - print("E浜戠瀹�-DifyAI瀵规帴鏈嶅姟闆嗘垚娴嬭瘯") - print("=" * 50) - print(f"娴嬭瘯鏃堕棿: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - print() - - tests = [ - ("鏈嶅姟鍋ュ悍妫�鏌�", test_service_health), - ("鍥炶皟API鍔熻兘", test_callback_api), - ("鏃犳晥娑堟伅杩囨护", test_invalid_message), - ("鑷彂娑堟伅杩囨护", test_self_message), - ] - - passed = 0 - total = len(tests) - - for test_name, test_func in tests: - print(f"姝e湪娴嬭瘯: {test_name}") - if test_func(): - passed += 1 - print() - - print("=" * 50) - print(f"娴嬭瘯缁撴灉: {passed}/{total} 閫氳繃") - - if passed == total: - print("馃帀 鎵�鏈夋祴璇曢�氳繃锛�") - return 0 - else: - print("鈿狅笍 閮ㄥ垎娴嬭瘯澶辫触锛岃妫�鏌ユ湇鍔¢厤缃�") - return 1 - - -if __name__ == "__main__": - exit(main()) diff --git a/tests/test_dify_streaming.py b/tests/test_dify_streaming.py new file mode 100644 index 0000000..8fa8630 --- /dev/null +++ b/tests/test_dify_streaming.py @@ -0,0 +1,174 @@ +""" +娴嬭瘯Dify娴佸紡妯″紡鍔熻兘 +""" + +import pytest +import json +from unittest.mock import Mock, patch, MagicMock +from app.services.dify_client import DifyClient +from config import settings + + +class TestDifyStreaming: + """娴嬭瘯Dify娴佸紡妯″紡""" + + def setup_method(self): + """娴嬭瘯鍓嶈缃�""" + self.client = DifyClient() + + def test_process_stream_response_success(self): + """娴嬭瘯鎴愬姛澶勭悊娴佸紡鍝嶅簲""" + # 妯℃嫙娴佸紡鍝嶅簲鏁版嵁 + stream_data = [ + "data: {\"event\": \"message\", \"task_id\": \"test-task\", \"id\": \"test-msg\", \"conversation_id\": \"test-conv\", \"answer\": \"Hello\", \"created_at\": 1705398420}", + "data: {\"event\": \"message\", \"task_id\": \"test-task\", \"id\": \"test-msg\", \"conversation_id\": \"test-conv\", \"answer\": \" World\", \"created_at\": 1705398420}", + "data: {\"event\": \"message_end\", \"metadata\": {\"usage\": {\"total_tokens\": 10}}, \"usage\": {\"total_tokens\": 10}}", + ] + + # 鍒涘缓妯℃嫙鍝嶅簲瀵硅薄 + mock_response = Mock() + mock_response.iter_lines.return_value = stream_data + + # 娴嬭瘯澶勭悊娴佸紡鍝嶅簲 + result = self.client._process_stream_response(mock_response, "test_user") + + # 楠岃瘉缁撴灉 + assert result is not None + assert result["answer"] == "Hello World" + assert result["conversation_id"] == "test-conv" + assert result["task_id"] == "test-task" + assert result["usage"]["total_tokens"] == 10 + + def test_process_stream_response_error(self): + """娴嬭瘯澶勭悊娴佸紡鍝嶅簲閿欒""" + # 妯℃嫙閿欒鍝嶅簲鏁版嵁 + stream_data = [ + "data: {\"event\": \"error\", \"message\": \"API璋冪敤澶辫触\", \"code\": \"500\"}", + ] + + # 鍒涘缓妯℃嫙鍝嶅簲瀵硅薄 + mock_response = Mock() + mock_response.iter_lines.return_value = stream_data + + # 娴嬭瘯澶勭悊娴佸紡鍝嶅簲 + result = self.client._process_stream_response(mock_response, "test_user") + + # 楠岃瘉缁撴灉 + assert result is None + + def test_process_stream_response_incomplete(self): + """娴嬭瘯澶勭悊涓嶅畬鏁寸殑娴佸紡鍝嶅簲""" + # 妯℃嫙涓嶅畬鏁村搷搴旀暟鎹紙缂哄皯message_end浜嬩欢锛� + stream_data = [ + "data: {\"event\": \"message\", \"task_id\": \"test-task\", \"id\": \"test-msg\", \"conversation_id\": \"test-conv\", \"answer\": \"Hello\", \"created_at\": 1705398420}", + ] + + # 鍒涘缓妯℃嫙鍝嶅簲瀵硅薄 + mock_response = Mock() + mock_response.iter_lines.return_value = stream_data + + # 娴嬭瘯澶勭悊娴佸紡鍝嶅簲 + result = self.client._process_stream_response(mock_response, "test_user") + + # 楠岃瘉缁撴灉 - 鍗充娇娌℃湁message_end浜嬩欢锛屽彧瑕佹湁鍐呭鍜宑onversation_id涔熷簲璇ヨ繑鍥炵粨鏋� + assert result is not None + assert result["answer"] == "Hello" + assert result["conversation_id"] == "test-conv" + + @patch('app.services.dify_client.settings') + def test_send_message_uses_streaming_when_enabled(self, mock_settings): + """娴嬭瘯褰撳惎鐢ㄦ祦寮忔ā寮忔椂浣跨敤娴佸紡鍙戦��""" + # 璁剧疆閰嶇疆涓哄惎鐢ㄦ祦寮忔ā寮� + mock_settings.dify_streaming_enabled = True + + # 妯℃嫙娴佸紡鍙戦�佹柟娉� + with patch.object(self.client, 'send_chat_message_stream') as mock_stream: + mock_stream.return_value = {"answer": "test response", "conversation_id": "test-conv"} + + result = self.client.send_message("test query", "test_user") + + # 楠岃瘉璋冪敤浜嗘祦寮忔柟娉� + mock_stream.assert_called_once_with("test query", "test_user", None, None) + assert result["answer"] == "test response" + + @patch('app.services.dify_client.settings') + def test_send_message_uses_blocking_when_disabled(self, mock_settings): + """娴嬭瘯褰撶鐢ㄦ祦寮忔ā寮忔椂浣跨敤闃诲鍙戦��""" + # 璁剧疆閰嶇疆涓虹鐢ㄦ祦寮忔ā寮� + mock_settings.dify_streaming_enabled = False + + # 妯℃嫙闃诲鍙戦�佹柟娉� + with patch.object(self.client, 'send_chat_message') as mock_blocking: + mock_blocking.return_value = {"answer": "test response", "conversation_id": "test-conv"} + + result = self.client.send_message("test query", "test_user") + + # 楠岃瘉璋冪敤浜嗛樆濉炴柟娉� + mock_blocking.assert_called_once_with("test query", "test_user", None, None) + assert result["answer"] == "test response" + + @patch('app.services.dify_client.settings') + def test_send_message_force_streaming_override(self, mock_settings): + """娴嬭瘯寮哄埗娴佸紡妯″紡瑕嗙洊閰嶇疆""" + # 璁剧疆閰嶇疆涓虹鐢ㄦ祦寮忔ā寮� + mock_settings.dify_streaming_enabled = False + + # 妯℃嫙娴佸紡鍙戦�佹柟娉� + with patch.object(self.client, 'send_chat_message_stream') as mock_stream: + mock_stream.return_value = {"answer": "test response", "conversation_id": "test-conv"} + + # 寮哄埗浣跨敤娴佸紡妯″紡 + result = self.client.send_message("test query", "test_user", force_streaming=True) + + # 楠岃瘉璋冪敤浜嗘祦寮忔柟娉曪紙瑕嗙洊浜嗛厤缃級 + mock_stream.assert_called_once_with("test query", "test_user", None, None) + assert result["answer"] == "test response" + + def test_process_stream_response_with_ping_events(self): + """娴嬭瘯澶勭悊鍖呭惈ping浜嬩欢鐨勬祦寮忓搷搴�""" + # 妯℃嫙鍖呭惈ping浜嬩欢鐨勫搷搴旀暟鎹� + stream_data = [ + "data: {\"event\": \"ping\"}", + "data: {\"event\": \"message\", \"task_id\": \"test-task\", \"id\": \"test-msg\", \"conversation_id\": \"test-conv\", \"answer\": \"Hello\", \"created_at\": 1705398420}", + "data: {\"event\": \"ping\"}", + "data: {\"event\": \"message\", \"task_id\": \"test-task\", \"id\": \"test-msg\", \"conversation_id\": \"test-conv\", \"answer\": \" World\", \"created_at\": 1705398420}", + "data: {\"event\": \"message_end\", \"metadata\": {\"usage\": {\"total_tokens\": 10}}}", + ] + + # 鍒涘缓妯℃嫙鍝嶅簲瀵硅薄 + mock_response = Mock() + mock_response.iter_lines.return_value = stream_data + + # 娴嬭瘯澶勭悊娴佸紡鍝嶅簲 + result = self.client._process_stream_response(mock_response, "test_user") + + # 楠岃瘉缁撴灉锛坧ing浜嬩欢搴旇琚拷鐣ワ級 + assert result is not None + assert result["answer"] == "Hello World" + assert result["conversation_id"] == "test-conv" + + def test_process_stream_response_with_invalid_json(self): + """娴嬭瘯澶勭悊鍖呭惈鏃犳晥JSON鐨勬祦寮忓搷搴�""" + # 妯℃嫙鍖呭惈鏃犳晥JSON鐨勫搷搴旀暟鎹� + stream_data = [ + "data: {\"event\": \"message\", \"task_id\": \"test-task\", \"id\": \"test-msg\", \"conversation_id\": \"test-conv\", \"answer\": \"Hello\", \"created_at\": 1705398420}", + "data: invalid json data", + "data: {\"event\": \"message\", \"task_id\": \"test-task\", \"id\": \"test-msg\", \"conversation_id\": \"test-conv\", \"answer\": \" World\", \"created_at\": 1705398420}", + "data: {\"event\": \"message_end\"}", + ] + + # 鍒涘缓妯℃嫙鍝嶅簲瀵硅薄 + mock_response = Mock() + mock_response.iter_lines.return_value = stream_data + + # 娴嬭瘯澶勭悊娴佸紡鍝嶅簲 + result = self.client._process_stream_response(mock_response, "test_user") + + # 楠岃瘉缁撴灉锛堟棤鏁圝SON搴旇琚烦杩囷級 + assert result is not None + assert result["answer"] == "Hello World" + assert result["conversation_id"] == "test-conv" + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/test_friend_ignore_service.py b/tests/test_friend_ignore_service.py new file mode 100644 index 0000000..6bc92a7 --- /dev/null +++ b/tests/test_friend_ignore_service.py @@ -0,0 +1,258 @@ +""" +濂藉弸蹇界暐鏈嶅姟娴嬭瘯 +""" + +import pytest +from unittest.mock import Mock, patch +from app.services.friend_ignore_service import FriendIgnoreService + + +class TestFriendIgnoreService: + """濂藉弸蹇界暐鏈嶅姟娴嬭瘯绫�""" + + def setup_method(self): + """娴嬭瘯鍓嶅噯澶�""" + self.service = FriendIgnoreService() + + @patch('app.services.friend_ignore_service.redis_queue') + def test_add_friends_to_ignore_list_success(self, mock_redis_queue): + """娴嬭瘯鎴愬姛娣诲姞濂藉弸鍒板拷鐣ュ垪琛�""" + # 妯℃嫙Redis鎿嶄綔 + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + + friends = ["wxid_test1", "wxid_test2", "wxid_test3"] + + result = self.service.add_friends_to_ignore_list(friends) + + # 楠岃瘉缁撴灉 + assert result is True + + # 楠岃瘉Redis鎿嶄綔琚皟鐢� + mock_redis_client.delete.assert_called_once_with(self.service.ignore_list_key) + mock_redis_client.sadd.assert_called_once_with(self.service.ignore_list_key, *friends) + + @patch('app.services.friend_ignore_service.redis_queue') + def test_add_friends_to_ignore_list_empty(self, mock_redis_queue): + """娴嬭瘯娣诲姞绌哄ソ鍙嬪垪琛�""" + result = self.service.add_friends_to_ignore_list([]) + + # 楠岃瘉缁撴灉 + assert result is True + + # 楠岃瘉Redis鎿嶄綔鏈璋冪敤 + mock_redis_queue.redis_client.delete.assert_not_called() + mock_redis_queue.redis_client.sadd.assert_not_called() + + @patch('app.services.friend_ignore_service.redis_queue') + def test_is_friend_ignored_true(self, mock_redis_queue): + """娴嬭瘯妫�鏌ュソ鍙嬪湪蹇界暐鍒楄〃涓�""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.sismember.return_value = True + + result = self.service.is_friend_ignored("wxid_test1") + + assert result is True + mock_redis_client.sismember.assert_called_once_with(self.service.ignore_list_key, "wxid_test1") + + @patch('app.services.friend_ignore_service.redis_queue') + def test_is_friend_ignored_false(self, mock_redis_queue): + """娴嬭瘯妫�鏌ュソ鍙嬩笉鍦ㄥ拷鐣ュ垪琛ㄤ腑""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.sismember.return_value = False + + result = self.service.is_friend_ignored("wxid_test1") + + assert result is False + mock_redis_client.sismember.assert_called_once_with(self.service.ignore_list_key, "wxid_test1") + + @patch('app.services.friend_ignore_service.redis_queue') + def test_get_ignore_list(self, mock_redis_queue): + """娴嬭瘯鑾峰彇蹇界暐鍒楄〃""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + expected_set = {"wxid_test1", "wxid_test2"} + mock_redis_client.smembers.return_value = expected_set + + result = self.service.get_ignore_list() + + assert result == expected_set + mock_redis_client.smembers.assert_called_once_with(self.service.ignore_list_key) + + @patch('app.services.friend_ignore_service.redis_queue') + def test_remove_friend_from_ignore_list(self, mock_redis_queue): + """娴嬭瘯浠庡拷鐣ュ垪琛ㄧЩ闄ゅソ鍙�""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.srem.return_value = 1 # 琛ㄧず鎴愬姛绉婚櫎 + + result = self.service.remove_friend_from_ignore_list("wxid_test1") + + assert result is True + mock_redis_client.srem.assert_called_once_with(self.service.ignore_list_key, "wxid_test1") + + @patch('app.services.friend_ignore_service.redis_queue') + def test_clear_ignore_list(self, mock_redis_queue): + """娴嬭瘯娓呯┖蹇界暐鍒楄〃""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + + result = self.service.clear_ignore_list() + + assert result is True + mock_redis_client.delete.assert_called_once_with(self.service.ignore_list_key) + + @patch('app.services.friend_ignore_service.redis_queue') + def test_get_ignore_list_count(self, mock_redis_queue): + """娴嬭瘯鑾峰彇蹇界暐鍒楄〃鏁伴噺""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.scard.return_value = 5 + + result = self.service.get_ignore_list_count() + + assert result == 5 + mock_redis_client.scard.assert_called_once_with(self.service.ignore_list_key) + + @patch('app.services.friend_ignore_service.redis_queue') + def test_add_friends_exception_handling(self, mock_redis_queue): + """娴嬭瘯娣诲姞濂藉弸鏃剁殑寮傚父澶勭悊""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.sadd.side_effect = Exception("Redis error") + + result = self.service.add_friends_to_ignore_list(["wxid_test1"]) + + assert result is False + + @patch('app.services.friend_ignore_service.redis_queue') + def test_is_friend_ignored_exception_handling(self, mock_redis_queue): + """娴嬭瘯妫�鏌ュソ鍙嬫椂鐨勫紓甯稿鐞�""" + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.sismember.side_effect = Exception("Redis error") + + result = self.service.is_friend_ignored("wxid_test1") + + assert result is False + + @patch('app.services.friend_ignore_service.settings') + @patch('app.services.friend_ignore_service.redis_queue') + @patch('app.services.friend_ignore_service.get_db') + def test_is_friend_ignored_whitelist(self, mock_get_db, mock_redis_queue, mock_settings): + """娴嬭瘯鐧藉悕鍗曞姛鑳�""" + # 妯℃嫙閰嶇疆 + mock_settings.friend_ignore_enabled = True + mock_settings.friend_ignore_whitelist = ["娴嬭瘯鐢ㄦ埛1", "娴嬭瘯鐢ㄦ埛2"] + + # 妯℃嫙鏁版嵁搴撴煡璇� + mock_db = Mock() + mock_get_db.return_value.__next__.return_value.__enter__.return_value = mock_db + mock_get_db.return_value.__next__.return_value.__exit__.return_value = None + + # 妯℃嫙鑱旂郴浜烘煡璇㈢粨鏋� - 鏍规嵁鏄电О杩斿洖涓嶅悓鐨勮仈绯讳汉 + def mock_query_side_effect(*args, **kwargs): + mock_query = Mock() + mock_filter = Mock() + mock_query.filter.return_value = mock_filter + + # 鏍规嵁鏌ヨ鏉′欢杩斿洖涓嶅悓鐨勭粨鏋� + def mock_first(): + # 杩欓噷绠�鍖栧鐞嗭紝鍋囪鏌ヨ"娴嬭瘯鐢ㄦ埛1"鏃惰繑鍥瀢xid_whitelist1 + mock_contact = Mock() + mock_contact.wc_id = "wxid_whitelist1" + return mock_contact + + mock_filter.first = mock_first + return mock_query + + mock_db.query.side_effect = mock_query_side_effect + + # 妯℃嫙Redis鎿嶄綔 + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.sismember.return_value = True # 鍦ㄥ拷鐣ュ垪琛ㄤ腑 + + # 娴嬭瘯鐧藉悕鍗曠敤鎴凤紙鍗充娇鍦ㄥ拷鐣ュ垪琛ㄤ腑涔熶笉搴旇琚拷鐣ワ級 + result = self.service.is_friend_ignored("wxid_whitelist1") + assert result is False + + # 娴嬭瘯闈炵櫧鍚嶅崟鐢ㄦ埛锛堝湪蹇界暐鍒楄〃涓簲璇ヨ蹇界暐锛� + result = self.service.is_friend_ignored("wxid_normal_user") + assert result is True + + @patch('app.services.friend_ignore_service.settings') + def test_is_friend_ignored_disabled(self, mock_settings): + """娴嬭瘯鍔熻兘绂佺敤鏃剁殑琛屼负""" + mock_settings.friend_ignore_enabled = False + mock_settings.friend_ignore_whitelist = [] + + result = self.service.is_friend_ignored("wxid_test1") + assert result is False + + @patch('app.services.friend_ignore_service.settings') + @patch('app.services.friend_ignore_service.redis_queue') + @patch('app.services.friend_ignore_service.get_db') + def test_get_ignore_status_info(self, mock_get_db, mock_redis_queue, mock_settings): + """娴嬭瘯鑾峰彇璇︾粏鐘舵�佷俊鎭�""" + mock_settings.friend_ignore_enabled = True + mock_settings.friend_ignore_whitelist = ["娴嬭瘯鐢ㄦ埛1"] + + # 妯℃嫙鏁版嵁搴撴煡璇� + mock_db = Mock() + mock_get_db.return_value.__enter__.return_value = mock_db + mock_get_db.return_value.__exit__.return_value = None + + # 妯℃嫙鑱旂郴浜烘煡璇㈢粨鏋� + mock_contact = Mock() + mock_contact.wc_id = "wxid_whitelist1" + mock_db.query.return_value.filter.return_value.first.return_value = mock_contact + + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.sismember.return_value = True + + # 娴嬭瘯鐧藉悕鍗曠敤鎴� + result = self.service.get_ignore_status_info("wxid_whitelist1") + assert result["w_id"] == "wxid_whitelist1" + assert result["in_whitelist"] is True + assert result["final_ignored"] is False + assert "鐧藉悕鍗�" in result["reason"] + assert result["whitelist_nicknames"] == ["娴嬭瘯鐢ㄦ埛1"] + + # 娴嬭瘯鏅�氱敤鎴� + result = self.service.get_ignore_status_info("wxid_normal_user") + assert result["w_id"] == "wxid_normal_user" + assert result["in_whitelist"] is False + assert result["in_ignore_list"] is True + assert result["final_ignored"] is True + + @patch('app.services.friend_ignore_service.FriendIgnoreService._get_whitelist_wids') + @patch('app.services.friend_ignore_service.settings') + @patch('app.services.friend_ignore_service.redis_queue') + def test_is_friend_ignored_nickname_whitelist(self, mock_redis_queue, mock_settings, mock_get_whitelist_wids): + """娴嬭瘯鏄电О鐧藉悕鍗曞姛鑳�""" + # 妯℃嫙閰嶇疆 + mock_settings.friend_ignore_enabled = True + mock_settings.friend_ignore_whitelist = ["娴嬭瘯鐢ㄦ埛1", "娴嬭瘯鐢ㄦ埛2"] + + # 妯℃嫙鐧藉悕鍗晈_id杞崲缁撴灉 + mock_get_whitelist_wids.return_value = ["wxid_whitelist1", "wxid_whitelist2"] + + # 妯℃嫙Redis鎿嶄綔 + mock_redis_client = Mock() + mock_redis_queue.redis_client = mock_redis_client + mock_redis_client.sismember.return_value = True # 鍦ㄥ拷鐣ュ垪琛ㄤ腑 + + # 娴嬭瘯鐧藉悕鍗曠敤鎴凤紙鍗充娇鍦ㄥ拷鐣ュ垪琛ㄤ腑涔熶笉搴旇琚拷鐣ワ級 + result = self.service.is_friend_ignored("wxid_whitelist1") + assert result is False + + # 娴嬭瘯闈炵櫧鍚嶅崟鐢ㄦ埛锛堝湪蹇界暐鍒楄〃涓簲璇ヨ蹇界暐锛� + result = self.service.is_friend_ignored("wxid_normal_user") + assert result is True + + # 楠岃瘉鏂规硶琚皟鐢� + assert mock_get_whitelist_wids.call_count >= 2 diff --git a/tests/test_message_processor.py b/tests/test_message_processor.py index e4fe1da..5e1eee2 100644 --- a/tests/test_message_processor.py +++ b/tests/test_message_processor.py @@ -71,6 +71,44 @@ result = self.processor.is_valid_group_message(callback_data) assert result is False + + @patch('app.services.message_processor.friend_ignore_service') + def test_is_valid_group_message_friend_ignored(self, mock_friend_ignore_service): + """娴嬭瘯濂藉弸鍦ㄥ拷鐣ュ垪琛ㄤ腑鐨勬秷鎭�""" + 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_friend_ignore_service.is_friend_ignored.assert_called_once_with("wxid_test123") + + @patch('app.services.message_processor.friend_ignore_service') + def test_is_valid_group_message_friend_not_ignored(self, mock_friend_ignore_service): + """娴嬭瘯濂藉弸涓嶅湪蹇界暐鍒楄〃涓殑娑堟伅""" + mock_friend_ignore_service.is_friend_ignored.return_value = False + + 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 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): diff --git a/uninstall_service.bat b/uninstall_service.bat deleted file mode 100644 index c361981..0000000 --- a/uninstall_service.bat +++ /dev/null @@ -1,61 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo E浜戠瀹�-DifyAI瀵规帴鏈嶅姟 Windows鏈嶅姟鍗歌浇鑴氭湰 -echo ======================================== -echo. - -:: 妫�鏌ョ鐞嗗憳鏉冮檺 -net session >nul 2>&1 -if %errorLevel% == 0 ( - echo 妫�娴嬪埌绠$悊鍛樻潈闄愶紝缁х画鍗歌浇... -) else ( - echo 閿欒锛氶渶瑕佺鐞嗗憳鏉冮檺鎵嶈兘鍗歌浇Windows鏈嶅姟 - echo 璇峰彸閿偣鍑绘鑴氭湰锛岄�夋嫨"浠ョ鐞嗗憳韬唤杩愯" - pause - exit /b 1 -) - -set "SERVICE_NAME=ECloudDifyService" - -echo 鏈嶅姟鍚嶇О: %SERVICE_NAME% -echo. - -:: 妫�鏌ユ湇鍔℃槸鍚﹀瓨鍦� -echo 妫�鏌ユ湇鍔℃槸鍚﹀瓨鍦�... -sc query "%SERVICE_NAME%" >nul 2>&1 -if %errorLevel% == 0 ( - echo 鎵惧埌鏈嶅姟锛屾鍦ㄥ嵏杞�... - - :: 鍋滄鏈嶅姟 - echo 姝e湪鍋滄鏈嶅姟... - sc stop "%SERVICE_NAME%" >nul 2>&1 - if %errorLevel% == 0 ( - echo 鏈嶅姟宸插仠姝� - ) else ( - echo 鏈嶅姟鍙兘宸茬粡鍋滄鎴栧仠姝㈠け璐� - ) - - :: 绛夊緟鏈嶅姟瀹屽叏鍋滄 - echo 绛夊緟鏈嶅姟瀹屽叏鍋滄... - timeout /t 5 /nobreak >nul - - :: 鍒犻櫎鏈嶅姟 - echo 姝e湪鍒犻櫎鏈嶅姟... - sc delete "%SERVICE_NAME%" - if %errorLevel% == 0 ( - echo 鏈嶅姟鍒犻櫎鎴愬姛锛� - ) else ( - echo 閿欒锛氭湇鍔″垹闄ゅけ璐� - pause - exit /b 1 - ) -) else ( - echo 鏈壘鍒版湇鍔� %SERVICE_NAME% - echo 鍙兘鏈嶅姟宸茬粡琚垹闄ゆ垨浠庢湭瀹夎 -) - -echo. -echo 鏈嶅姟鍗歌浇瀹屾垚锛� -echo. -pause -- Gitblit v1.9.1