10个文件已添加
14个文件已修改
1个文件已删除
| | |
| | | |
| | | # 忽ç¥ç¹å®æä»¶ |
| | | config.ini |
| | | config.json |
| | | secrets.json |
| | | |
| | | # å¿½ç¥æ´ä¸ªç®å½ |
| | |
| | | .idea/ |
| | | |
| | | # ä¾å¤ï¼ä¸å¿½ç¥ dist ç®å½ä¸ç important.js |
| | | !dist/important.js |
| | | !dist/important.js |
| | | |
| | | # åæ¶å¿½ç¥ææ .pyc æä»¶ï¼å¯é使¨èï¼ |
| | | *.pyc |
| | | *.pyo |
| | | *.pyd |
New file |
| | |
| | | # é
ç½®æä»¶ä½¿ç¨æå |
| | | |
| | | ## æ¦è¿° |
| | | |
| | | 项ç®å·²ä»Pythoné
ç½®æä»¶ï¼config.pyï¼è½¬æ¢ä¸ºJSONé
ç½®æä»¶ï¼config.jsonï¼ï¼è¿æ ·æ´éåæå
为exeæä»¶ã |
| | | |
| | | ## é
ç½®æä»¶ç»æ |
| | | |
| | | ### 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`: æ°æ®åºè¿æ¥åç¬¦ä¸²ï¼æ¯æMySQL |
| | | |
| | | ### Redisé
ç½® (redis) |
| | | - `url`: Redisè¿æ¥å符串 |
| | | |
| | | ### Eäºç®¡å®¶é
ç½® (ecloud) |
| | | - `base_url`: Eäºç®¡å®¶APIåºç¡URL |
| | | - `authorization`: Eäºç®¡å®¶APIææä»¤ç |
| | | |
| | | ### 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` |
| | | - ç产ç¯å¢ï¼exeæä»¶åç®å½ä¸ç `config.json` |
| | | |
| | | ### 3. é
ç½®éªè¯ |
| | | 妿é
ç½®æä»¶ä¸åå¨ææ ¼å¼é误ï¼ç³»ç»ä¼ä½¿ç¨é»è®¤é
置并è¾åºé误信æ¯ã |
| | | |
| | | ## å
¼å®¹æ§ |
| | | |
| | | - åæç `from config import settings` 导å
¥æ¹å¼ä¿æä¸å |
| | | - ææé
ç½®å±æ§çè®¿é®æ¹å¼ä¿æä¸åï¼å¦ `settings.database_url`ï¼ |
| | | - ååå
¼å®¹ï¼ä¸éè¦ä¿®æ¹ç°æä»£ç |
| | | |
| | | ## æå
为exeçä¼å¿ |
| | | |
| | | 1. **é
ç½®å¤é¨å**: é
ç½®æä»¶ç¬ç«äºexeæä»¶ï¼ä¾¿äºé¨ç½²æ¶ä¿®æ¹ |
| | | 2. **æ ééæ°ç¼è¯**: ä¿®æ¹é
ç½®ä¸éè¦éæ°æå
exe |
| | | 3. **æäºç»´æ¤**: JSONæ ¼å¼ç´è§æè¯»ï¼ä¾¿äºè¿ç»´äººåé
ç½® |
| | | 4. **çæ¬æ§å¶å好**: å¯ä»¥ä¸ºä¸åç¯å¢åå¤ä¸åçé
ç½®æä»¶ |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | 1. ç¡®ä¿ `config.json` æä»¶æ ¼å¼æ£ç¡®ï¼å¯ä»¥ä½¿ç¨JSONéªè¯å·¥å
·æ£æ¥ |
| | | 2. ææä¿¡æ¯ï¼å¦æ°æ®åºå¯ç ãAPIå¯é¥ï¼åºå¦¥åä¿ç®¡ |
| | | 3. ç产ç¯å¢å»ºè®®å° `debug` 设置为 `false` |
| | | 4. æ¥å¿æä»¶è·¯å¾ç¡®ä¿åºç¨æåå
¥æé |
New file |
| | |
| | | # Eäºç®¡å®¶-DifyAIå¯¹æ¥æå¡ Windowsé¨ç½²æå |
| | | |
| | | ## æ¦è¿° |
| | | |
| | | æ¬ææ¡£ä»ç»å¦ä½å¨Windowsæå¡å¨ä¸é¨ç½²Eäºç®¡å®¶-DifyAIå¯¹æ¥æå¡çexeçæ¬ã |
| | | |
| | | ## ç³»ç»è¦æ± |
| | | |
| | | - Windows Server 2016 ææ´é«çæ¬ |
| | | - Windows 10/11 (ç¨äºæµè¯) |
| | | - è³å° 2GB å¯ç¨å
å |
| | | - è³å° 1GB å¯ç¨ç£çç©ºé´ |
| | | - ç½ç»è¿æ¥ï¼ç¨äºè®¿é®æ°æ®åºãRedisãEäºç®¡å®¶å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äºç®¡å®¶APIå°å", |
| | | "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. å®è£
åå¯å¨æå¡ |
| | | |
| | | #### æ¹å¼ä¸ï¼Windowsæå¡æ¨¡å¼ï¼æ¨èï¼ |
| | | |
| | | 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://æå¡å¨IP:7979/health` |
| | | - åºè¯¥è¿åå¥åº·æ£æ¥ä¿¡æ¯ |
| | | |
| | | 2. **æ¥çæ¥å¿**ï¼ |
| | | - æ¥å¿æä»¶ä½äº `logs/app.log` |
| | | - æ£æ¥æ¯å¦æéè¯¯ä¿¡æ¯ |
| | | |
| | | 3. **æµè¯API**ï¼ |
| | | - è®¿é® `http://æå¡å¨IP:7979/` |
| | | - åºè¯¥è¿åæå¡ä¿¡æ¯ |
| | | |
| | | ## é²ç«å¢é
ç½® |
| | | |
| | | ç¡®ä¿Windowsé²ç«å¢å
许端å£7979çå
¥ç«è¿æ¥ï¼ |
| | | |
| | | ```cmd |
| | | # æ·»å é²ç«å¢è§å |
| | | netsh advfirewall firewall add rule name="ECloudDify Service" dir=in action=allow protocol=TCP localport=7979 |
| | | ``` |
| | | |
| | | ## æ
éæé¤ |
| | | |
| | | ### 常è§é®é¢ |
| | | |
| | | 1. **æå¡å¯å¨å¤±è´¥**ï¼ |
| | | - æ£æ¥é
ç½®æä»¶æ ¼å¼æ¯å¦æ£ç¡® |
| | | - ç¡®è®¤æ°æ®åºåRedisè¿æ¥æ¯å¦æ£å¸¸ |
| | | - æ¥çæ¥å¿æä»¶è·å详ç»éè¯¯ä¿¡æ¯ |
| | | |
| | | 2. **端å£è¢«å ç¨**ï¼ |
| | | - ä¿®æ¹é
ç½®æä»¶ä¸ç端å£å· |
| | | - æè
忢å ç¨ç«¯å£çå
¶ä»ç¨åº |
| | | |
| | | 3. **æéé®é¢**ï¼ |
| | | - ç¡®ä¿ä»¥ç®¡çå身份è¿è¡å®è£
èæ¬ |
| | | - æ£æ¥exeæä»¶çæ§è¡æé |
| | | |
| | | 4. **ç½ç»è¿æ¥é®é¢**ï¼ |
| | | - æ£æ¥é²ç«å¢è®¾ç½® |
| | | - 确认ç½ç»è¿æ¥æ£å¸¸ |
| | | |
| | | ### æ¥å¿æ¥ç |
| | | |
| | | æ¥å¿æä»¶ä½ç½®ï¼`logs/app.log` |
| | | |
| | | å¸¸ç¨æ¥å¿çº§å«ï¼ |
| | | - INFO: ä¸è¬ä¿¡æ¯ |
| | | - WARNING: è¦åä¿¡æ¯ |
| | | - ERROR: éè¯¯ä¿¡æ¯ |
| | | |
| | | ## æ§è½ä¼å |
| | | |
| | | 1. **å
åä¼å**ï¼ |
| | | - çæ§å
åä½¿ç¨æ
åµ |
| | | - å¿
è¦æ¶è°æ´ç³»ç»å
ååé
|
| | | |
| | | 2. **ç½ç»ä¼å**ï¼ |
| | | - ç¡®ä¿ç½ç»å»¶è¿è¾ä½ |
| | | - 使ç¨é«éç½ç»è¿æ¥ |
| | | |
| | | 3. **æ°æ®åºä¼å**ï¼ |
| | | - ä¼åæ°æ®åºè¿æ¥æ± 设置 |
| | | - 宿æ¸
çæ¥å¿æ°æ® |
| | | |
| | | ## å®å
¨å»ºè®® |
| | | |
| | | 1. **é
ç½®æä»¶å®å
¨**ï¼ |
| | | - ä¿æ¤é
ç½®æä»¶ä¸çææä¿¡æ¯ |
| | | - 设置éå½çæä»¶æé |
| | | |
| | | 2. **ç½ç»å®å
¨**ï¼ |
| | | - 使ç¨HTTPSï¼å¦ææ¯æï¼ |
| | | - éå¶è®¿é®IPèå´ |
| | | |
| | | 3. **å®ææ´æ°**ï¼ |
| | | - å®ææ´æ°æå¡çæ¬ |
| | | - å
³æ³¨å®å
¨è¡¥ä¸ |
| | | |
| | | ## èç³»æ¯æ |
| | | |
| | | å¦éå°é®é¢ï¼è¯·æä¾ä»¥ä¸ä¿¡æ¯ï¼ |
| | | - é误æ¥å¿å
容 |
| | | - é
ç½®æä»¶ï¼éèææä¿¡æ¯ï¼ |
| | | - ç³»ç»ç¯å¢ä¿¡æ¯ |
| | | - é®é¢å¤ç°æ¥éª¤ |
| | |
| | | |
| | | åæ°å å¿
é ç±»å 说æ |
| | | wId æ¯ String ç»å½å®ä¾æ è¯ |
| | | wcId æ¯ String 好å微信id/群id |
| | | wcId æ¯ String 好å微信id/群idï¼å¤ä¸ªä½¿ç¨è±æéå·åé |
| | | |
| | | 请æ±åæ°ç¤ºä¾ |
| | | |
| | |
| | | "message": "失败", |
| | | "code": "1001", |
| | | "data": null |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | # åå§åé讯å½å表æ¥å£ |
| | | |
| | | ç®è¦æè¿°ï¼ |
| | | åå§åé讯å½å表 |
| | | |
| | | 请æ±URLï¼ |
| | | http://ååå°å/initAddressList |
| | | |
| | | è¯·æ±æ¹å¼ï¼ |
| | | POST |
| | | |
| | | 请æ±å¤´Headersï¼ |
| | | Content-Typeï¼application/json |
| | | Authorizationï¼{Authorization} |
| | | |
| | | åæ°ï¼ |
| | | åæ°å å¿
é ç±»å 说æ |
| | | wId æ¯ String ç»å½å®ä¾æ è¯ |
| | | |
| | | 请æ±åæ°ç¤ºä¾ |
| | | { |
| | | "wId": "6a696578-16ea-4edc-ac8b-e609bca39c69" |
| | | } |
| | | |
| | | æåè¿åç¤ºä¾ |
| | | { |
| | | "message": "æå", |
| | | "code": "1000", |
| | | "data": null |
| | | } |
| | | |
| | | é误è¿åç¤ºä¾ |
| | | { |
| | | "message": "失败", |
| | | "code": "1001", |
| | | "data": null |
| | | } |
| | | |
| | | è¿åæ°æ®ï¼ |
| | | åæ°å ç±»å 说æ |
| | | code string 1000æåã1001失败 |
| | | msg string åé¦ä¿¡æ¯ |
| | | data JSONObject æ |
| | | |
| | | |
| | | |
| | | # è·åé讯å½å表æ¥å£ |
| | | ç®è¦æè¿°ï¼ |
| | | è·åé讯å½å表 |
| | | |
| | | 请æ±URLï¼ |
| | | http://ååå°å/getAddressList |
| | | |
| | | è¯·æ±æ¹å¼ï¼ |
| | | POST |
| | | |
| | | 请æ±å¤´Headersï¼ |
| | | Content-Typeï¼application/json |
| | | Authorizationï¼{Authorization} |
| | | |
| | | åæ°ï¼ |
| | | |
| | | åæ°å å¿
é ç±»å 说æ |
| | | wId æ¯ String ç»å½å®ä¾æ è¯ |
| | | å°æç¤ºï¼ |
| | | è·åé讯å½å表ä¹åï¼å¿
é¡»è°ç¨åå§åé讯å½å表æ¥å£ã |
| | | |
| | | 请æ±åæ°ç¤ºä¾ |
| | | { |
| | | "wId": "6a696578-16ea-4edc-ac8b-e609bca39c69" |
| | | } |
| | | |
| | | æåè¿åç¤ºä¾ |
| | | { |
| | | "code": "1000", |
| | | "message": "è·åéè®¯å½æå", |
| | | "data": { |
| | | "chatrooms": [ |
| | | "" |
| | | ], |
| | | "friends": [ |
| | | "" |
| | | ], |
| | | "ghs": [ |
| | | "" |
| | | ], |
| | | "others": [ |
| | | "" |
| | | ] |
| | | } |
| | | } |
| | | |
| | | é误è¿åç¤ºä¾ |
| | | { |
| | | "message": "失败", |
| | | "code": "1001", |
| | | "data": null |
| | | } |
| | | |
| | | è¿åæ°æ®ï¼ |
| | | |
| | | åæ°å ç±»å 说æ |
| | | code String 1000æå |
| | | 1001失败 |
| | | msg String åé¦ä¿¡æ¯ |
| | | data JSONObject |
| | | chatrooms JSONArray 群ç»å表 |
| | | friends JSONArray 好åå表 |
| | | ghs JSONArray å
¬ä¼å·å表 |
| | | others JSONArray 微信å
¶ä»ç¸å
³ |
| | | |
| | | |
| | | |
| | | # 群è@æ¥å£ |
| | | 请æ±URLï¼ |
| | | http://ååå°å/sendText |
| | | |
| | | è¯·æ±æ¹å¼ï¼ |
| | | POST |
| | | |
| | | 请æ±å¤´Headersï¼ |
| | | Content-Typeï¼application/json |
| | | Authorizationï¼{Authorization} |
| | | |
| | | åæ°ï¼ |
| | | åæ°å å¿
é ç±»å 说æ |
| | | wId æ¯ string ç»å½å®ä¾æ è¯ |
| | | wcId æ¯ string æ¥æ¶æ¹ç¾¤id |
| | | content æ¯ string ææ¬å
å®¹æ¶æ¯ï¼@ç微信æµç§°éè¦èªå·±æ¼æ¥ï¼å¿
é¡»æ¼æ¥è¾ç¹ç¬¦å·ï¼ä¸ç¶ä¸çæï¼ |
| | | at æ¯ string è¾ç¹ç微信idï¼å¤ä¸ªä»¥éå·åå¼ï¼ |
| | | |
| | | è¿åæ°æ®ï¼ |
| | | åæ°å ç±»å 说æ |
| | | code string 1000æåï¼1001失败 |
| | | msg string åé¦ä¿¡æ¯ |
| | | data |
| | | data.type int ç±»å |
| | | data.msgId long æ¶æ¯msgId |
| | | data.newMsgId long æ¶æ¯newMsgId |
| | | data.createTime long æ¶æ¯åéæ¶é´æ³ |
| | | data.wcId string æ¶æ¯æ¥æ¶æ¹id |
| | | |
| | | 请æ±åæ°ç¤ºä¾ |
| | | { |
| | | "wId": "0000016f-8911-484a-0001-db2943fc2786", |
| | | "wcId": "22270365143@chatroom", |
| | | "at": "wxid_lr6j4nononb921,wxid_i6qsbbjenjuj22", |
| | | "content": "@EäºTeam_Mr Li@ä½ å¾®ç¬æ¶çç¾ æµè¯" |
| | | } |
| | | |
| | | æåè¿åç¤ºä¾ |
| | | { |
| | | "code": "1000", |
| | | "message": "å¤çæå", |
| | | "data": { |
| | | "type": 1, |
| | | "msgId": 2562652205, |
| | | "newMsgId": 4482117376572170921, |
| | | "createTime": 1641457769, |
| | | "wcId": "22270365143@chatroom" |
| | | } |
| | | } |
| | | |
| | | é误è¿åç¤ºä¾ |
| | | { |
| | | "message": "失败", |
| | | "code": "1001", |
| | | "data": null |
| | | } |
| | |
| | | |
| | | ### conversationsï¼å¯¹è¯è®°å½è¡¨ï¼ |
| | | - `id`: ä¸»é® |
| | | - `user_conversation_key`: ç¨æ·å¯¹è¯å¯ä¸é® |
| | | - `from_user`: åéç¨æ·å¾®ä¿¡ID |
| | | - `conversation_id`: Dify对è¯ID |
| | | - `user_question`: ç¨æ·æé® |
| | | - `ai_answer`: AIåç |
| | | - `group`: 群ç»ID |
| | | - `hour`: å°æ¶æ è¯(YYYYMMDD_HH) |
| | | - `content`: 对è¯å
容(JSONæ ¼å¼) |
| | | - `is_processed`: æ¯å¦å·²å¤ç |
| | | - `is_sent`: æ¯å¦å·²åé |
| | | - å¯ä¸æ§çº¦æ: (from_user, conversation_id, group, hour) |
| | | |
| | | ## é
置说æ |
| | | |
| | |
| | | # å°æ¶æ¯å å
¥éå |
| | | success = message_processor.enqueue_callback_message(callback_dict) |
| | | |
| | | logger.info(f"æ¶æ¯å
¥éç»æ: success={success}") |
| | | |
| | | if success: |
| | | # è·ååéç¨æ·ID |
| | | from_user = callback_dict.get("data", {}).get("fromUser") |
| | | if from_user: |
| | | logger.info(f"å¯å¨ç¨æ·éåå¤ç: from_user={from_user}") |
| | | # å¯å¨ç¨æ·éåå¤ç |
| | | message_worker.process_user_queue(from_user) |
| | | |
| | |
| | | success=True, message="æ¶æ¯å·²æåå å
¥å¤çéå", code=200 |
| | | ) |
| | | else: |
| | | logger.warning("æ¶æ¯å¤çå¤±è´¥ï¼æªè½å å
¥éå") |
| | | return CallbackResponse(success=False, message="æ¶æ¯å¤ç失败", code=400) |
| | | |
| | | except Exception as e: |
| | |
| | | """ |
| | | 对è¯è®°å½æ¨¡å |
| | | """ |
| | | from sqlalchemy import Column, String, Integer, DateTime, Text, Boolean |
| | | |
| | | from sqlalchemy import Column, String, Integer, DateTime, Text, Boolean, UniqueConstraint |
| | | from sqlalchemy.sql import func |
| | | from .database import Base |
| | | |
| | | |
| | | class Conversation(Base): |
| | | """对è¯è®°å½è¡¨""" |
| | | |
| | | __tablename__ = "conversations" |
| | | |
| | | |
| | | id = Column(Integer, primary_key=True, index=True, autoincrement=True) |
| | | user_conversation_key = Column(String(200), unique=True, index=True, nullable=False, |
| | | comment="ç¨æ·å¯¹è¯å¯ä¸é®(fromUser+conversation_id)") |
| | | from_user = Column(String(100), index=True, nullable=False, comment="åéç¨æ·å¾®ä¿¡ID") |
| | | from_user = Column( |
| | | String(100), index=True, nullable=False, comment="åéç¨æ·å¾®ä¿¡ID" |
| | | ) |
| | | conversation_id = Column(String(100), nullable=False, comment="Dify对è¯ID") |
| | | |
| | | # ç¨æ·é®é¢ |
| | | user_question = Column(Text, nullable=False, comment="ç¨æ·æé®å
容") |
| | | |
| | | # AIåç |
| | | ai_answer = Column(Text, nullable=True, comment="AIåçå
容") |
| | | |
| | | group = Column(String(100), nullable=False, comment="群ç»ID") |
| | | hour = Column(String(20), nullable=False, comment="å°æ¶æ è¯(YYYYMMDD_HH)") |
| | | |
| | | # 对è¯å
容ï¼JSONæ ¼å¼ï¼ |
| | | content = Column( |
| | | Text, |
| | | nullable=True, |
| | | comment='对è¯å
容JSONæ ¼å¼ï¼å¦[{"user":"ä½ å¥½","ai":"ä½ å¥½æä»ä¹è½å¸®å©ä½ ç"}]', |
| | | ) |
| | | |
| | | # æ¶æ¯ç¶æ |
| | | is_processed = Column(Boolean, default=False, comment="æ¯å¦å·²å¤ç") |
| | | is_sent = Column(Boolean, default=False, comment="æ¯å¦å·²åéåå¤") |
| | | |
| | | |
| | | # æ¶é´æ³ |
| | | question_time = Column(DateTime, default=func.now(), comment="æé®æ¶é´") |
| | | answer_time = Column(DateTime, nullable=True, comment="åçæ¶é´") |
| | | sent_time = Column(DateTime, nullable=True, comment="åéæ¶é´") |
| | | |
| | | |
| | | created_at = Column(DateTime, default=func.now(), comment="å建æ¶é´") |
| | | updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="æ´æ°æ¶é´") |
| | | |
| | | updated_at = Column( |
| | | DateTime, default=func.now(), onupdate=func.now(), comment="æ´æ°æ¶é´" |
| | | ) |
| | | |
| | | # æ·»å ååæ®µç»åçå¯ä¸æ§çº¦æ |
| | | __table_args__ = ( |
| | | UniqueConstraint('from_user', 'conversation_id', 'group', 'hour', |
| | | name='uq_conversation_key'), |
| | | ) |
| | | |
| | | def __repr__(self): |
| | | return f"<Conversation(from_user='{self.from_user}', conversation_id='{self.conversation_id}')>" |
| | | return f"<Conversation(from_user='{self.from_user}', conversation_id='{self.conversation_id}', group='{self.group}', hour='{self.hour}')>" |
New file |
| | |
| | | """ |
| | | èç³»äººåæ¥æå¡ |
| | | """ |
| | | |
| | | from typing import List, Optional |
| | | from loguru import logger |
| | | from sqlalchemy.orm import Session |
| | | from app.models.contact import Contact |
| | | from app.models.database import get_db |
| | | from app.services.ecloud_client import ecloud_client |
| | | |
| | | |
| | | class ContactSyncService: |
| | | """èç³»äººåæ¥æå¡""" |
| | | |
| | | def __init__(self): |
| | | pass |
| | | |
| | | def sync_contacts_on_startup(self, w_id: str) -> bool: |
| | | """ |
| | | å¯å¨æ¶åæ¥èç³»äººä¿¡æ¯ |
| | | |
| | | Args: |
| | | w_id: ç»å½å®ä¾æ è¯ |
| | | |
| | | Returns: |
| | | 忥æåè¿åTrueï¼å¤±è´¥è¿åFalse |
| | | """ |
| | | try: |
| | | logger.info(f"å¼å§åæ¥è系人信æ¯: wId={w_id}") |
| | | |
| | | # 1. åå§åé讯å½å表 |
| | | if not ecloud_client.init_address_list(w_id): |
| | | logger.error(f"åå§åé讯å½å表失败: wId={w_id}") |
| | | return False |
| | | |
| | | # 2. è·åé讯å½å表 |
| | | address_data = ecloud_client.get_address_list(w_id) |
| | | if not address_data: |
| | | logger.error(f"è·åé讯å½å表失败: wId={w_id}") |
| | | return False |
| | | |
| | | # 3. è·å好åå表ä¸çwcid |
| | | friends = address_data.get("friends", []) |
| | | if not friends: |
| | | logger.warning(f"好åå表为空: wId={w_id}") |
| | | return True |
| | | |
| | | logger.info(f"è·åå°å¥½åå表: wId={w_id}, count={len(friends)}") |
| | | |
| | | # 4. æ¹éè·åè系人详ç»ä¿¡æ¯ |
| | | return self._batch_sync_contacts(w_id, friends) |
| | | |
| | | except Exception as e: |
| | | logger.error(f"忥è系人信æ¯å¼å¸¸: wId={w_id}, error={str(e)}") |
| | | return False |
| | | |
| | | def _batch_sync_contacts(self, w_id: str, wc_ids: List[str]) -> bool: |
| | | """ |
| | | æ¹é忥èç³»äººä¿¡æ¯ |
| | | |
| | | Args: |
| | | w_id: ç»å½å®ä¾æ è¯ |
| | | wc_ids: 微信IDå表 |
| | | |
| | | Returns: |
| | | 忥æåè¿åTrueï¼å¤±è´¥è¿åFalse |
| | | """ |
| | | try: |
| | | # å°wcidå表ç¨éå·æ¼æ¥ |
| | | wc_ids_str = ",".join(wc_ids) |
| | | logger.info(f"å¼å§æ¹éè·åè系人信æ¯: wId={w_id}, wc_ids_count={len(wc_ids)}") |
| | | |
| | | # è°ç¨è·åèç³»äººä¿¡æ¯æ¥å£ |
| | | contact_info = ecloud_client.get_contact_info(w_id, wc_ids_str) |
| | | if not contact_info: |
| | | logger.error(f"æ¹éè·åè系人信æ¯å¤±è´¥: wId={w_id}") |
| | | return False |
| | | |
| | | # 妿è¿åçæ¯å个è系人信æ¯ï¼è½¬æ¢ä¸ºå表 |
| | | if isinstance(contact_info, dict): |
| | | contact_list = [contact_info] |
| | | else: |
| | | contact_list = contact_info |
| | | |
| | | logger.info(f"è·åå°è系人详ç»ä¿¡æ¯: count={len(contact_list)}") |
| | | |
| | | # ä¿åå°æ°æ®åº |
| | | return self._save_contacts_to_db(contact_list) |
| | | |
| | | except Exception as e: |
| | | logger.error(f"æ¹é忥è系人信æ¯å¼å¸¸: wId={w_id}, error={str(e)}") |
| | | return False |
| | | |
| | | def _save_contacts_to_db(self, contact_list: List[dict]) -> bool: |
| | | """ |
| | | ä¿åè系人信æ¯å°æ°æ®åº |
| | | |
| | | Args: |
| | | contact_list: è系人信æ¯å表 |
| | | |
| | | Returns: |
| | | ä¿åæåè¿åTrueï¼å¤±è´¥è¿åFalse |
| | | """ |
| | | try: |
| | | with next(get_db()) as db: |
| | | saved_count = 0 |
| | | updated_count = 0 |
| | | |
| | | for contact_data in contact_list: |
| | | wc_id = contact_data.get("userName") or contact_data.get("wcId") |
| | | if not wc_id: |
| | | logger.warning(f"è系人信æ¯ç¼ºå°wcId: {contact_data}") |
| | | continue |
| | | |
| | | # æ£æ¥æ¯å¦å·²åå¨ |
| | | existing_contact = db.query(Contact).filter(Contact.wc_id == wc_id).first() |
| | | |
| | | if existing_contact: |
| | | # æ´æ°ç°æèç³»äººä¿¡æ¯ |
| | | existing_contact.user_name = contact_data.get("userName") |
| | | existing_contact.nick_name = contact_data.get("nickName") |
| | | existing_contact.remark = contact_data.get("remark") |
| | | existing_contact.signature = contact_data.get("signature") |
| | | existing_contact.sex = contact_data.get("sex") |
| | | existing_contact.alias_name = contact_data.get("aliasName") |
| | | existing_contact.country = contact_data.get("country") |
| | | existing_contact.big_head = contact_data.get("bigHead") |
| | | existing_contact.small_head = contact_data.get("smallHead") |
| | | existing_contact.label_list = contact_data.get("labelList") |
| | | existing_contact.v1 = contact_data.get("v1") |
| | | updated_count += 1 |
| | | logger.debug(f"æ´æ°è系人信æ¯: wc_id={wc_id}, nick_name={contact_data.get('nickName')}") |
| | | else: |
| | | # å建æ°èç³»äººè®°å½ |
| | | new_contact = Contact( |
| | | wc_id=wc_id, |
| | | user_name=contact_data.get("userName"), |
| | | nick_name=contact_data.get("nickName"), |
| | | remark=contact_data.get("remark"), |
| | | signature=contact_data.get("signature"), |
| | | sex=contact_data.get("sex"), |
| | | alias_name=contact_data.get("aliasName"), |
| | | country=contact_data.get("country"), |
| | | big_head=contact_data.get("bigHead"), |
| | | small_head=contact_data.get("smallHead"), |
| | | label_list=contact_data.get("labelList"), |
| | | v1=contact_data.get("v1"), |
| | | ) |
| | | db.add(new_contact) |
| | | saved_count += 1 |
| | | logger.debug(f"æ°å¢è系人信æ¯: wc_id={wc_id}, nick_name={contact_data.get('nickName')}") |
| | | |
| | | # æäº¤äºå¡ |
| | | db.commit() |
| | | logger.info(f"è系人信æ¯ä¿å宿: æ°å¢={saved_count}, æ´æ°={updated_count}") |
| | | return True |
| | | |
| | | except Exception as e: |
| | | logger.error(f"ä¿åè系人信æ¯å°æ°æ®åºå¼å¸¸: error={str(e)}") |
| | | if 'db' in locals(): |
| | | db.rollback() |
| | | return False |
| | | |
| | | |
| | | # å
¨å±èç³»äººåæ¥æå¡å®ä¾ |
| | | contact_sync_service = ContactSyncService() |
| | |
| | | """ |
| | | |
| | | import requests |
| | | from typing import Optional, Dict, Any |
| | | from typing import Optional, Dict, Any, List |
| | | from loguru import logger |
| | | from config import settings |
| | | |
| | |
| | | |
| | | Args: |
| | | w_id: ç»å½å®ä¾æ è¯ |
| | | wc_id: 好å微信id/群id |
| | | wc_id: 好å微信id/群idï¼å¤ä¸ªä½¿ç¨è±æéå·åé |
| | | |
| | | Returns: |
| | | è系人信æ¯åå
¸ï¼å¤±è´¥è¿åNone |
| | | è系人信æ¯åå
¸æå表ï¼å¤±è´¥è¿åNone |
| | | - å个wcIdæ¶è¿ååå
¸ |
| | | - å¤ä¸ªwcIdæ¶è¿åå表 |
| | | """ |
| | | try: |
| | | url = f"{self.base_url}/getContact" |
| | |
| | | if result.get("code") == "1000": |
| | | contact_data = result.get("data", []) |
| | | if contact_data and len(contact_data) > 0: |
| | | logger.info(f"æåè·åè系人信æ¯: wcId={wc_id}") |
| | | return contact_data[0] # è¿å第ä¸ä¸ªèç³»äººä¿¡æ¯ |
| | | # 妿æ¯å个wcIdï¼è¿å第ä¸ä¸ªèç³»äººä¿¡æ¯ |
| | | if "," not in wc_id: |
| | | logger.info(f"æåè·åè系人信æ¯: wcId={wc_id}") |
| | | return contact_data[0] |
| | | else: |
| | | # 妿æ¯å¤ä¸ªwcIdï¼è¿å宿´å表 |
| | | logger.info(f"æåè·åæ¹éè系人信æ¯: count={len(contact_data)}") |
| | | return contact_data |
| | | else: |
| | | logger.warning(f"è系人信æ¯ä¸ºç©º: wcId={wc_id}") |
| | | return None |
| | |
| | | logger.error(f"è·åè系人信æ¯å¼å¸¸: wcId={wc_id}, error={str(e)}") |
| | | return None |
| | | |
| | | def send_text_message(self, w_id: str, wc_id: str, content: str) -> bool: |
| | | def send_text_message(self, w_id: str, wc_id: str, content: str, max_retries: int = None) -> bool: |
| | | """ |
| | | åéææ¬æ¶æ¯ |
| | | |
| | |
| | | w_id: ç»å½å®ä¾æ è¯ |
| | | wc_id: æ¥æ¶äººå¾®ä¿¡id/群id |
| | | content: ææ¬å
å®¹æ¶æ¯ |
| | | max_retries: æå¤§éè¯æ¬¡æ° |
| | | |
| | | Returns: |
| | | åéæåè¿åTrueï¼å¤±è´¥è¿åFalse |
| | | """ |
| | | try: |
| | | url = f"{self.base_url}/sendText" |
| | | payload = {"wId": w_id, "wcId": wc_id, "content": 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} |
| | | |
| | | logger.info( |
| | | f"åéææ¬æ¶æ¯: wId={w_id}, wcId={wc_id}, content_length={len(content)}" |
| | | ) |
| | | |
| | | response = self.session.post(url, json=payload, timeout=30) |
| | | response.raise_for_status() |
| | | |
| | | result = response.json() |
| | | |
| | | if result.get("code") == "1000": |
| | | logger.info(f"ææ¬æ¶æ¯åéæå: wcId={wc_id}") |
| | | return True |
| | | else: |
| | | logger.error( |
| | | f"ææ¬æ¶æ¯åé失败: wcId={wc_id}, code={result.get('code')}, message={result.get('message')}" |
| | | logger.info( |
| | | f"åéææ¬æ¶æ¯: wId={w_id}, wcId={wc_id}, content_length={len(content)}, retry={retry_count}" |
| | | ) |
| | | return False |
| | | |
| | | except requests.exceptions.RequestException as e: |
| | | logger.error(f"åéææ¬æ¶æ¯ç½ç»é误: wcId={wc_id}, error={str(e)}") |
| | | return False |
| | | except Exception as e: |
| | | logger.error(f"åéææ¬æ¶æ¯å¼å¸¸: wcId={wc_id}, error={str(e)}") |
| | | return False |
| | | response = self.session.post(url, json=payload, timeout=30) |
| | | response.raise_for_status() |
| | | |
| | | result = response.json() |
| | | |
| | | if result.get("code") == "1000": |
| | | logger.info(f"ææ¬æ¶æ¯åéæå: wcId={wc_id}") |
| | | return True |
| | | else: |
| | | logger.error( |
| | | f"ææ¬æ¶æ¯åé失败: wcId={wc_id}, code={result.get('code')}, message={result.get('message')}" |
| | | ) |
| | | |
| | | except requests.exceptions.RequestException as e: |
| | | logger.error(f"åéææ¬æ¶æ¯ç½ç»é误: wcId={wc_id}, retry={retry_count}, error={str(e)}") |
| | | except Exception as e: |
| | | logger.error(f"åéææ¬æ¶æ¯å¼å¸¸: wcId={wc_id}, retry={retry_count}, error={str(e)}") |
| | | |
| | | retry_count += 1 |
| | | if retry_count <= max_retries: |
| | | from config import settings |
| | | wait_time = settings.retry_delay * retry_count |
| | | logger.info(f"çå¾
éè¯: wcId={wc_id}, wait_time={wait_time}s") |
| | | import time |
| | | time.sleep(wait_time) |
| | | |
| | | logger.error( |
| | | f"ææ¬æ¶æ¯åé失败ï¼å·²è¾¾æå¤§éè¯æ¬¡æ°: wcId={wc_id}, max_retries={max_retries}" |
| | | ) |
| | | return False |
| | | |
| | | def send_group_message(self, w_id: str, group_id: str, content: str) -> bool: |
| | | """ |
| | |
| | | """ |
| | | return self.send_text_message(w_id, group_id, content) |
| | | |
| | | def init_address_list(self, w_id: str) -> bool: |
| | | """ |
| | | åå§åé讯å½å表 |
| | | |
| | | Args: |
| | | w_id: ç»å½å®ä¾æ è¯ |
| | | |
| | | Returns: |
| | | åå§åæåè¿åTrueï¼å¤±è´¥è¿åFalse |
| | | """ |
| | | try: |
| | | url = f"{self.base_url}/initAddressList" |
| | | payload = {"wId": w_id} |
| | | |
| | | logger.info(f"åå§åé讯å½å表: wId={w_id}") |
| | | |
| | | response = self.session.post(url, json=payload, timeout=30) |
| | | response.raise_for_status() |
| | | |
| | | result = response.json() |
| | | |
| | | if result.get("code") == "1000": |
| | | logger.info(f"åå§åé讯å½å表æå: wId={w_id}") |
| | | return True |
| | | else: |
| | | logger.error( |
| | | f"åå§åé讯å½å表失败: wId={w_id}, code={result.get('code')}, message={result.get('message')}" |
| | | ) |
| | | return False |
| | | |
| | | except requests.exceptions.RequestException as e: |
| | | logger.error(f"åå§åé讯å½å表ç½ç»é误: wId={w_id}, error={str(e)}") |
| | | return False |
| | | except Exception as e: |
| | | logger.error(f"åå§åé讯å½å表å¼å¸¸: wId={w_id}, error={str(e)}") |
| | | return False |
| | | |
| | | def get_address_list(self, w_id: str) -> Optional[Dict[str, Any]]: |
| | | """ |
| | | è·åé讯å½å表 |
| | | |
| | | Args: |
| | | w_id: ç»å½å®ä¾æ è¯ |
| | | |
| | | Returns: |
| | | éè®¯å½æ°æ®åå
¸ï¼å¤±è´¥è¿åNone |
| | | è¿åæ ¼å¼: { |
| | | "chatrooms": [...], # 群ç»å表 |
| | | "friends": [...], # 好åå表 |
| | | "ghs": [...], # å
¬ä¼å·å表 |
| | | "others": [...] # å
¶ä» |
| | | } |
| | | """ |
| | | try: |
| | | url = f"{self.base_url}/getAddressList" |
| | | payload = {"wId": w_id} |
| | | |
| | | logger.info(f"è·åé讯å½å表: wId={w_id}") |
| | | |
| | | response = self.session.post(url, json=payload, timeout=30) |
| | | response.raise_for_status() |
| | | |
| | | result = response.json() |
| | | |
| | | if result.get("code") == "1000": |
| | | address_data = result.get("data", {}) |
| | | logger.info(f"æåè·åé讯å½å表: wId={w_id}, friends_count={len(address_data.get('friends', []))}") |
| | | return address_data |
| | | else: |
| | | logger.error( |
| | | f"è·åé讯å½å表失败: wId={w_id}, code={result.get('code')}, message={result.get('message')}" |
| | | ) |
| | | return None |
| | | |
| | | except requests.exceptions.RequestException as e: |
| | | logger.error(f"è·åé讯å½å表ç½ç»é误: wId={w_id}, error={str(e)}") |
| | | return None |
| | | except Exception as e: |
| | | logger.error(f"è·åé讯å½å表å¼å¸¸: wId={w_id}, error={str(e)}") |
| | | return None |
| | | |
| | | def send_group_at_message(self, w_id: str, wc_id: str, content: str, at_wc_ids: List[str], max_retries: int = None) -> bool: |
| | | """ |
| | | åé群è@æ¶æ¯ |
| | | |
| | | Args: |
| | | w_id: ç»å½å®ä¾æ è¯ |
| | | wc_id: æ¥æ¶æ¹ç¾¤id |
| | | content: ææ¬å
å®¹æ¶æ¯ï¼@ç微信æµç§°éè¦èªå·±æ¼æ¥ï¼å¿
é¡»æ¼æ¥è¾ç¹ç¬¦å·ï¼ä¸ç¶ä¸çæï¼ |
| | | at_wc_ids: è¾ç¹ç微信idå表 |
| | | max_retries: æå¤§éè¯æ¬¡æ° |
| | | |
| | | Returns: |
| | | åéæåè¿åTrueï¼å¤±è´¥è¿åFalse |
| | | """ |
| | | 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" |
| | | # å°at_wc_idså表ç¨éå·æ¼æ¥ |
| | | at_str = ",".join(at_wc_ids) |
| | | payload = { |
| | | "wId": w_id, |
| | | "wcId": wc_id, |
| | | "content": content, |
| | | "at": at_str |
| | | } |
| | | |
| | | logger.info( |
| | | f"åé群è@æ¶æ¯: wId={w_id}, wcId={wc_id}, at={at_str}, content_length={len(content)}, retry={retry_count}" |
| | | ) |
| | | |
| | | response = self.session.post(url, json=payload, timeout=30) |
| | | response.raise_for_status() |
| | | |
| | | result = response.json() |
| | | |
| | | if result.get("code") == "1000": |
| | | logger.info(f"群è@æ¶æ¯åéæå: wcId={wc_id}, at={at_str}") |
| | | return True |
| | | else: |
| | | logger.error( |
| | | f"群è@æ¶æ¯åé失败: wcId={wc_id}, at={at_str}, code={result.get('code')}, message={result.get('message')}" |
| | | ) |
| | | |
| | | except requests.exceptions.RequestException as e: |
| | | logger.error(f"åé群è@æ¶æ¯ç½ç»é误: wcId={wc_id}, at={at_str}, retry={retry_count}, error={str(e)}") |
| | | except Exception as e: |
| | | logger.error(f"åé群è@æ¶æ¯å¼å¸¸: wcId={wc_id}, at={at_str}, retry={retry_count}, error={str(e)}") |
| | | |
| | | retry_count += 1 |
| | | if retry_count <= max_retries: |
| | | from config import settings |
| | | wait_time = settings.retry_delay * retry_count |
| | | logger.info(f"çå¾
éè¯: wcId={wc_id}, wait_time={wait_time}s") |
| | | import time |
| | | time.sleep(wait_time) |
| | | |
| | | logger.error( |
| | | f"群è@æ¶æ¯åé失败ï¼å·²è¾¾æå¤§éè¯æ¬¡æ°: wcId={wc_id}, at={at_str}, max_retries={max_retries}" |
| | | ) |
| | | return False |
| | | |
| | | |
| | | # å
¨å±Eäºç®¡å®¶å®¢æ·ç«¯å®ä¾ |
| | | ecloud_client = ECloudClient() |
| | |
| | | """ |
| | | |
| | | import json |
| | | from typing import Dict, Any, Optional |
| | | import time |
| | | import re |
| | | from typing import Dict, Any, Optional, List, Tuple |
| | | from datetime import datetime |
| | | from sqlalchemy.orm import Session |
| | | from loguru import logger |
| | | |
| | |
| | | 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 config import settings |
| | | |
| | | |
| | | class MessageProcessor: |
| | |
| | | |
| | | def __init__(self): |
| | | pass |
| | | |
| | | def parse_at_mentions(self, ai_answer: str) -> Tuple[str, List[str]]: |
| | | """ |
| | | è§£æAIåå¤ä¸ç@åç¬¦ï¼æå客æåç§° |
| | | |
| | | Args: |
| | | ai_answer: AIåå¤å
容 |
| | | |
| | | Returns: |
| | | (å¤çåçæ¶æ¯å
容, éè¦@ç客æwcidå表) |
| | | """ |
| | | try: |
| | | # è·åé
ç½®ç客æåç§°å表 |
| | | customer_service_names = settings.customer_service_names |
| | | |
| | | # æ¥æ¾ææ@å符åç客æåç§° |
| | | at_pattern = r'@([^\s]+)' |
| | | matches = re.findall(at_pattern, ai_answer) |
| | | |
| | | valid_at_names = [] |
| | | at_wc_ids = [] |
| | | |
| | | for match in matches: |
| | | # æ£æ¥æ¯å¦å¨é
ç½®ç客æåç§°åè¡¨ä¸ |
| | | if match in customer_service_names: |
| | | valid_at_names.append(match) |
| | | logger.info(f"åç°ææç@客æåç§°: {match}") |
| | | |
| | | # å¦ææææç@客æåç§°ï¼æ¥è¯¢æ°æ®åºè·åwcid |
| | | if valid_at_names: |
| | | with next(get_db()) as db: |
| | | for name in valid_at_names: |
| | | # æ ¹æ®nick_nameæ¥æ¾è系人 |
| | | contact = db.query(Contact).filter(Contact.nick_name == name).first() |
| | | if contact: |
| | | at_wc_ids.append(contact.wc_id) |
| | | logger.info(f"æ¾å°å®¢æè系人: name={name}, wc_id={contact.wc_id}") |
| | | else: |
| | | logger.warning(f"æªæ¾å°å®¢æè系人: name={name}") |
| | | |
| | | return ai_answer, at_wc_ids |
| | | |
| | | except Exception as e: |
| | | logger.error(f"è§£æ@å符å¼å¸¸: error={str(e)}") |
| | | return ai_answer, [] |
| | | |
| | | def is_valid_group_message(self, callback_data: Dict[str, Any]) -> bool: |
| | | """ |
| | |
| | | |
| | | logger.info(f"å¼å§å¤çæ¶æ¯: from_user={from_user}, from_group={from_group}") |
| | | |
| | | # è·åæ°æ®åºä¼è¯ |
| | | db = next(get_db()) |
| | | |
| | | try: |
| | | # 使ç¨ä¸ä¸æç®¡çå¨ç¡®ä¿æ°æ®åºä¼è¯æ£ç¡®ç®¡ç |
| | | with next(get_db()) as db: |
| | | # 3.1 ç¡®ä¿è系人信æ¯åå¨ |
| | | if not self.ensure_contact_exists(from_group, w_id, db): |
| | | logger.error(f"è系人信æ¯å¤ç失败: from_group={from_group}") |
| | | return False |
| | | |
| | | # 3.2 è·åç¨æ·çconversation_id |
| | | conversation_id = redis_queue.get_conversation_id(from_user) |
| | | # 3.2 è·åç¨æ·å¨å½å群ç»çconversation_id |
| | | conversation_id = redis_queue.get_conversation_id(from_user, from_group) |
| | | |
| | | # è°ç¨Difyæ¥å£åéæ¶æ¯ |
| | | dify_response = dify_client.send_chat_message( |
| | |
| | | ai_answer = dify_response.get("answer", "") |
| | | new_conversation_id = dify_response.get("conversation_id", "") |
| | | |
| | | # æ´æ°Redisä¸çconversation_id |
| | | # æ´æ°Redisä¸çconversation_idï¼åºäºç¨æ·+群ç»ï¼ |
| | | if new_conversation_id: |
| | | redis_queue.set_conversation_id(from_user, new_conversation_id) |
| | | redis_queue.set_conversation_id(from_user, new_conversation_id, from_group) |
| | | |
| | | # 3.3 ä¿å对è¯è®°å½å°æ°æ®åº |
| | | user_conversation_key = f"{from_user}_{new_conversation_id}" |
| | | # æç¨æ·ã群ç»åå°æ¶åç»å¯¹è¯è®°å½ |
| | | current_time = datetime.now() |
| | | hour_key = current_time.strftime("%Y%m%d_%H") |
| | | |
| | | # æ£æ¥æ¯å¦å·²åå¨è®°å½ |
| | | # æ¥æ¾å½åç¨æ·å¨å½å群ç»å½åå°æ¶ç对è¯è®°å½ |
| | | existing_conversation = ( |
| | | db.query(Conversation) |
| | | .filter(Conversation.user_conversation_key == user_conversation_key) |
| | | .filter( |
| | | Conversation.from_user == from_user, |
| | | Conversation.conversation_id == new_conversation_id, |
| | | Conversation.group == from_group, |
| | | Conversation.hour == hour_key |
| | | ) |
| | | .first() |
| | | ) |
| | | |
| | | if existing_conversation: |
| | | # æ´æ°ç°æè®°å½ |
| | | existing_conversation.user_question = content |
| | | existing_conversation.ai_answer = ai_answer |
| | | existing_conversation.is_processed = True |
| | | logger.info(f"æ´æ°å¯¹è¯è®°å½: key={user_conversation_key}") |
| | | # æ´æ°ç°æè®°å½ - 使ç¨JSONæ ¼å¼è¿½å 对è¯å
容ï¼å½åç¨æ·å¨å½å群ç»å½åå°æ¶ç对è¯ï¼ |
| | | try: |
| | | # è§£æç°æçcontent JSON |
| | | if existing_conversation.content: |
| | | content_list = json.loads(existing_conversation.content) |
| | | else: |
| | | content_list = [] |
| | | |
| | | # è¿½å æ°ç对è¯å
容 |
| | | content_list.append({ |
| | | "user": content, |
| | | "ai": ai_answer |
| | | }) |
| | | |
| | | # æ´æ°è®°å½ |
| | | existing_conversation.content = json.dumps(content_list, ensure_ascii=False) |
| | | existing_conversation.is_processed = True |
| | | logger.info(f"追å å°å½åç¨æ·ç¾¤ç»å°æ¶å¯¹è¯è®°å½: user={from_user}, group={from_group}, hour={hour_key}, 对è¯è½®æ¬¡={len(content_list)}") |
| | | except json.JSONDecodeError as e: |
| | | logger.error(f"è§£æç°æå¯¹è¯å
容JSON失败: {str(e)}, éæ°å建") |
| | | # 妿JSONè§£æå¤±è´¥ï¼éæ°å建content |
| | | content_list = [{"user": content, "ai": ai_answer}] |
| | | existing_conversation.content = json.dumps(content_list, ensure_ascii=False) |
| | | existing_conversation.is_processed = True |
| | | else: |
| | | # å建æ°è®°å½ |
| | | # å建æ°è®°å½ - æ°çç¨æ·ç¾¤ç»å°æ¶å¯¹è¯æé¦æ¬¡å¯¹è¯ï¼ä½¿ç¨JSONæ ¼å¼åå¨å¯¹è¯å
容 |
| | | content_list = [{"user": content, "ai": ai_answer}] |
| | | new_conversation = Conversation( |
| | | user_conversation_key=user_conversation_key, |
| | | from_user=from_user, |
| | | conversation_id=new_conversation_id, |
| | | user_question=content, |
| | | ai_answer=ai_answer, |
| | | group=from_group, |
| | | hour=hour_key, |
| | | content=json.dumps(content_list, ensure_ascii=False), |
| | | is_processed=True, |
| | | ) |
| | | db.add(new_conversation) |
| | | logger.info(f"å建对è¯è®°å½: key={user_conversation_key}") |
| | | logger.info(f"å建æ°çç¨æ·ç¾¤ç»å°æ¶å¯¹è¯è®°å½: user={from_user}, group={from_group}, hour={hour_key}, åå§å¯¹è¯è½®æ¬¡=1") |
| | | |
| | | db.commit() |
| | | |
| | | # åéAIåçå°ç¾¤è |
| | | if ai_answer and ecloud_client.send_group_message( |
| | | w_id, from_group, ai_answer |
| | | ): |
| | | success = False |
| | | if ai_answer: |
| | | # è§£æAIåå¤ä¸ç@å符 |
| | | processed_answer, at_wc_ids = self.parse_at_mentions(ai_answer) |
| | | |
| | | # åéæ¶æ¯ï¼æå¤éè¯3次 |
| | | for attempt in range(3): |
| | | if at_wc_ids: |
| | | # 妿æ@客æï¼ä½¿ç¨ç¾¤è@æ¥å£ |
| | | logger.info(f"使ç¨ç¾¤è@æ¥å£åéæ¶æ¯: at_wc_ids={at_wc_ids}") |
| | | if ecloud_client.send_group_at_message( |
| | | w_id, from_group, processed_answer, at_wc_ids |
| | | ): |
| | | success = True |
| | | break |
| | | else: |
| | | # æ®éç¾¤èæ¶æ¯ |
| | | logger.info("ä½¿ç¨æ®éç¾¤èæ¥å£åéæ¶æ¯") |
| | | if ecloud_client.send_group_message( |
| | | w_id, from_group, processed_answer |
| | | ): |
| | | success = True |
| | | break |
| | | |
| | | logger.warning(f"åéAIåç失败ï¼å°è¯éè¯ ({attempt + 1}/3): from_user={from_user}") |
| | | if attempt < 2: # 䏿¯æå䏿¬¡å°è¯ï¼çå¾
䏿®µæ¶é´åéè¯ |
| | | time.sleep(2 ** attempt) # ææ°éé¿ |
| | | |
| | | if success: |
| | | # æ´æ°åéç¶æ |
| | | conversation = ( |
| | | db.query(Conversation) |
| | | .filter( |
| | | Conversation.user_conversation_key == user_conversation_key |
| | | Conversation.from_user == from_user, |
| | | Conversation.conversation_id == new_conversation_id, |
| | | Conversation.group == from_group, |
| | | Conversation.hour == hour_key |
| | | ) |
| | | .first() |
| | | ) |
| | | if conversation: |
| | | conversation.is_sent = True |
| | | conversation.sent_time = current_time |
| | | db.commit() |
| | | |
| | | logger.info(f"æ¶æ¯å¤ç宿: from_user={from_user}") |
| | |
| | | else: |
| | | logger.error(f"åéAIåç失败: from_user={from_user}") |
| | | return False |
| | | |
| | | finally: |
| | | db.close() |
| | | |
| | | except Exception as e: |
| | | logger.error(f"å¤çæ¶æ¯å¼å¸¸: from_user={from_user}, error={str(e)}") |
| | |
| | | """è·åå¤çç¶æé®""" |
| | | return f"{self.processing_prefix}{from_user}" |
| | | |
| | | def get_conversation_key(self, from_user: str) -> str: |
| | | def get_conversation_key(self, from_user: str, group: str = None) -> str: |
| | | """è·åç¨æ·å¯¹è¯IDé®""" |
| | | return f"{self.conversation_prefix}{from_user}" |
| | | if group: |
| | | # åºäºç¨æ·+群ç»çæconversation_keyï¼ç¡®ä¿ä¸å群ç»ç对è¯ç¬ç« |
| | | return f"{self.conversation_prefix}{from_user}:{group}" |
| | | else: |
| | | # å
¼å®¹æ§çæ¬ï¼ä»
åºäºç¨æ· |
| | | return f"{self.conversation_prefix}{from_user}" |
| | | |
| | | def enqueue_message(self, from_user: str, message_data: Dict[str, Any]) -> bool: |
| | | """å°æ¶æ¯å å
¥ç¨æ·éå""" |
| | |
| | | queue_key = self.get_user_queue_key(from_user) |
| | | message_json = json.dumps(message_data, ensure_ascii=False) |
| | | |
| | | # 使ç¨LPUSHå°æ¶æ¯å å
¥éåå¤´é¨ |
| | | result = self.redis_client.lpush(queue_key, message_json) |
| | | # 使ç¨RPUSHå°æ¶æ¯å å
¥éåå°¾é¨ï¼ç¡®ä¿FIFOé¡ºåº |
| | | result = self.redis_client.rpush(queue_key, message_json) |
| | | |
| | | logger.info(f"æ¶æ¯å·²å å
¥éå: user={from_user}, queue_length={result}, message={message_json}") |
| | | return True |
| | |
| | | logger.error(f"æ£æ¥å¤çç¶æå¤±è´¥: user={from_user}, error={str(e)}") |
| | | return False |
| | | |
| | | def get_conversation_id(self, from_user: str) -> Optional[str]: |
| | | def get_conversation_id(self, from_user: str, group: str = None) -> Optional[str]: |
| | | """è·åç¨æ·ç对è¯ID""" |
| | | try: |
| | | conversation_key = self.get_conversation_key(from_user) |
| | | conversation_key = self.get_conversation_key(from_user, group) |
| | | return self.redis_client.get(conversation_key) |
| | | except Exception as e: |
| | | logger.error(f"è·å对è¯ID失败: user={from_user}, error={str(e)}") |
| | | logger.error(f"è·å对è¯ID失败: user={from_user}, group={group}, error={str(e)}") |
| | | return None |
| | | |
| | | def set_conversation_id( |
| | | self, from_user: str, conversation_id: str, ttl: int = 86400 |
| | | self, from_user: str, conversation_id: str, group: str = None, ttl: int = 86400 |
| | | ) -> bool: |
| | | """è®¾ç½®ç¨æ·ç对è¯ID""" |
| | | try: |
| | | conversation_key = self.get_conversation_key(from_user) |
| | | conversation_key = self.get_conversation_key(from_user, group) |
| | | self.redis_client.setex(conversation_key, ttl, conversation_id) |
| | | logger.info( |
| | | f"设置对è¯ID: user={from_user}, conversation_id={conversation_id}" |
| | | f"设置对è¯ID: user={from_user}, group={group}, conversation_id={conversation_id}" |
| | | ) |
| | | return True |
| | | except Exception as e: |
| | | logger.error(f"设置对è¯ID失败: user={from_user}, error={str(e)}") |
| | | logger.error(f"设置对è¯ID失败: user={from_user}, group={group}, error={str(e)}") |
| | | return False |
| | | |
| | | def get_queue_length(self, from_user: str) -> int: |
| | |
| | | level=settings.log_level, |
| | | format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", |
| | | rotation="1 day", |
| | | retention="30 days", |
| | | retention="7 days", |
| | | compression="zip", |
| | | encoding="utf-8" |
| | | ) |
| | | |
| | | |
| | | # æ·»å é误æ¥å¿æä»¶ |
| | | error_log_file = settings.log_file.replace('.log', '_error.log') |
| | | logger.add( |
| | |
| | | level="ERROR", |
| | | format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", |
| | | rotation="1 day", |
| | | retention="30 days", |
| | | retention="7 days", |
| | | compression="zip", |
| | | encoding="utf-8" |
| | | ) |
| | |
| | | """çæ§éåï¼ä¸ºææ¶æ¯çç¨æ·å¯å¨å¤ç线ç¨""" |
| | | while self.running: |
| | | try: |
| | | # è¿éå¯ä»¥å®ç°æ´å¤æçéååç°æºå¶ |
| | | # ç®åç®åä¸ºæ£æ¥å·²ç¥çæ´»è·ç¨æ· |
| | | # å¨å®é
åºç¨ä¸ï¼å¯ä»¥éè¿RedisçSCANå½ä»¤æ«æææéå |
| | | |
| | | # 使ç¨RedisçSCANå½ä»¤æ«æææéåé® |
| | | cursor = 0 |
| | | queue_keys = set() |
| | | |
| | | while self.running: |
| | | cursor, keys = self._scan_queue_keys(cursor) |
| | | queue_keys.update(keys) |
| | | |
| | | # å¦ææ¸¸æ 为0ï¼è¡¨ç¤ºæ«æå®æ |
| | | if cursor == 0: |
| | | break |
| | | |
| | | # 为æ¯ä¸ªææ¶æ¯çéåå¯å¨å¤ççº¿ç¨ |
| | | for queue_key in queue_keys: |
| | | if not self.running: |
| | | break |
| | | |
| | | # ä»éåé®ä¸æåç¨æ·ID |
| | | if queue_key.startswith(redis_queue.queue_prefix): |
| | | from_user = queue_key[len(redis_queue.queue_prefix):] |
| | | |
| | | # æ£æ¥éåæ¯å¦ææ¶æ¯ |
| | | queue_length = redis_queue.get_queue_length(from_user) |
| | | if queue_length > 0: |
| | | logger.info(f"åç°ç¨æ·éåææ¶æ¯: user={from_user}, length={queue_length}") |
| | | # å¯å¨ç¨æ·éåå¤ç |
| | | self.process_user_queue(from_user) |
| | | |
| | | # æ¸
ç已宿ççº¿ç¨ |
| | | self._cleanup_finished_threads() |
| | | |
| | |
| | | except Exception as e: |
| | | logger.error(f"éåçæ§å¼å¸¸: {str(e)}") |
| | | time.sleep(10) |
| | | |
| | | def _scan_queue_keys(self, cursor: int = 0, count: int = 100) -> tuple: |
| | | """ |
| | | æ«æéåé® |
| | | |
| | | Args: |
| | | cursor: æ«ææ¸¸æ |
| | | count: æ¯æ¬¡æ«æç鮿°é |
| | | |
| | | Returns: |
| | | (æ°æ¸¸æ , éåé®å表) |
| | | """ |
| | | try: |
| | | # 使ç¨SCANå½ä»¤æ«æå¹é
模å¼çé® |
| | | new_cursor, keys = redis_queue.redis_client.scan( |
| | | cursor=cursor, |
| | | match=f"{redis_queue.queue_prefix}*", |
| | | count=count |
| | | ) |
| | | return new_cursor, keys |
| | | except Exception as e: |
| | | logger.error(f"æ«æéåé®å¤±è´¥: {str(e)}") |
| | | return 0, [] |
| | | |
| | | def _cleanup_finished_threads(self): |
| | | """æ¸
ç已宿ç线ç¨""" |
| | |
| | | """ |
| | | try: |
| | | logger.info(f"å¼å§å¤çç¨æ·æ¶æ¯éå: {from_user}") |
| | | |
| | | # è·åéåé¿åº¦ |
| | | queue_length = redis_queue.get_queue_length(from_user) |
| | | logger.info(f"ç¨æ·éååå§é¿åº¦: {from_user}, length={queue_length}") |
| | | |
| | | while self.running: |
| | | # æ£æ¥ç¨æ·æ¯å¦æ£å¨å¤çä¸ï¼é²æ¢å¹¶åï¼ |
| | |
| | | |
| | | if success: |
| | | logger.info(f"æ¶æ¯å¤çæå: {from_user}") |
| | | # æ´æ°éåé¿åº¦ä¿¡æ¯ |
| | | queue_length = redis_queue.get_queue_length(from_user) |
| | | logger.info(f"ç¨æ·éåå©ä½é¿åº¦: {from_user}, length={queue_length}") |
| | | else: |
| | | logger.error(f"æ¶æ¯å¤ç失败: {from_user}") |
| | | # å¯ä»¥èèå°å¤±è´¥çæ¶æ¯éæ°å
¥éæè®°å½å°é误éå |
New file |
| | |
| | | { |
| | | "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"] |
| | | } |
| | | } |
New file |
| | |
| | | { |
| | | "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 |
| | | } |
| | | } |
| | |
| | | """ |
| | | |
| | | import os |
| | | from typing import Optional |
| | | from pydantic_settings import BaseSettings |
| | | import json |
| | | |
| | | |
| | | class Settings(BaseSettings): |
| | | class Settings: |
| | | """åºç¨é
ç½®""" |
| | | |
| | | # æ°æ®åºé
ç½® |
| | | database_url: str = "mysql+pymysql://root:TAI%402019%23Zjun@120.24.39.179:3306/ecloud_dify" |
| | | def __init__(self, config_file: str = "config.json"): |
| | | """åå§åé
ç½®ï¼ä»JSONæä»¶å è½½""" |
| | | self.config_file = config_file |
| | | self._load_config() |
| | | |
| | | # Redisé
ç½® |
| | | redis_url: str = "redis://localhost:6379/0" |
| | | def _load_config(self): |
| | | """ä»JSONæä»¶å è½½é
ç½®""" |
| | | 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() |
| | | except Exception as e: |
| | | print(f"å è½½é
ç½®æä»¶å¤±è´¥: {e}") |
| | | self._set_default_config() |
| | | |
| | | # Eäºç®¡å®¶é
ç½® |
| | | ecloud_base_url: str = "http://your-ecloud-domain.com" |
| | | ecloud_authorization: str = "your-authorization-token" |
| | | 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") |
| | | |
| | | # DifyAIé
ç½® |
| | | dify_base_url: str = "https://api.dify.ai/v1" |
| | | dify_api_key: str = "your-dify-api-key" |
| | | # Redisé
ç½® |
| | | self.redis_url = config_data.get("redis", {}).get("url", "redis://localhost:6379/0") |
| | | |
| | | # æå¡é
ç½® |
| | | server_host: str = "0.0.0.0" |
| | | server_port: int = 8000 |
| | | debug: bool = True |
| | | # 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", "") |
| | | |
| | | # æ¥å¿é
ç½® |
| | | log_level: str = "INFO" |
| | | log_file: str = "logs/app.log" |
| | | # 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") |
| | | |
| | | # æ¶æ¯å¤çé
ç½® |
| | | max_retry_count: int = 3 |
| | | retry_delay: int = 5 # ç§ |
| | | queue_timeout: int = 300 # ç§ |
| | | # æå¡é
ç½® |
| | | 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) |
| | | |
| | | class Config: |
| | | env_file = ".env" |
| | | env_file_encoding = "utf-8" |
| | | # æ¥å¿é
ç½® |
| | | logging_config = config_data.get("logging", {}) |
| | | self.log_level = logging_config.get("level", "INFO") |
| | | self.log_file = logging_config.get("file", "logs/app.log") |
| | | |
| | | # æ¶æ¯å¤çé
ç½® |
| | | 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) |
| | | |
| | | # 客æé
ç½® |
| | | customer_service_config = config_data.get("customer_service", {}) |
| | | self.customer_service_names = customer_service_config.get("names", ["客æ1", "客æ2"]) |
| | | |
| | | 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"] |
| | | |
| | | |
| | | # å
¨å±é
ç½®å®ä¾ |
New file |
| | |
| | | # -*- mode: python ; coding: utf-8 -*- |
| | | import os |
| | | import sys |
| | | from PyInstaller.utils.hooks import collect_data_files, collect_submodules |
| | | |
| | | # è·åé¡¹ç®æ ¹ç®å½ |
| | | project_root = os.path.abspath('.') |
| | | |
| | | # æ¶éæææ°æ®æä»¶ |
| | | datas = [] |
| | | |
| | | # æ·»å é
ç½®æä»¶ |
| | | datas.append(('config.example.json', '.')) |
| | | datas.append(('config.production.json', '.')) |
| | | if os.path.exists('config.json'): |
| | | datas.append(('config.json', '.')) |
| | | |
| | | # æ·»å æ¥å¿ç®å½ |
| | | if os.path.exists('logs'): |
| | | datas.append(('logs', 'logs')) |
| | | |
| | | # æ¶éFastAPIåå
¶ä»å
çæ°æ®æä»¶ |
| | | datas.extend(collect_data_files('fastapi')) |
| | | datas.extend(collect_data_files('uvicorn')) |
| | | datas.extend(collect_data_files('pydantic')) |
| | | datas.extend(collect_data_files('sqlalchemy')) |
| | | |
| | | # æ¶ééè导å
¥ |
| | | hiddenimports = [] |
| | | hiddenimports.extend(collect_submodules('uvicorn')) |
| | | hiddenimports.extend(collect_submodules('fastapi')) |
| | | hiddenimports.extend(collect_submodules('pydantic')) |
| | | hiddenimports.extend(collect_submodules('sqlalchemy')) |
| | | hiddenimports.extend(collect_submodules('pymysql')) |
| | | hiddenimports.extend(collect_submodules('redis')) |
| | | hiddenimports.extend(collect_submodules('celery')) |
| | | hiddenimports.extend(collect_submodules('loguru')) |
| | | hiddenimports.extend(collect_submodules('cryptography')) |
| | | hiddenimports.extend(collect_submodules('requests')) |
| | | hiddenimports.extend(collect_submodules('httpx')) |
| | | |
| | | # æ·»å ç¹å®çéè导å
¥ |
| | | hiddenimports.extend([ |
| | | 'uvicorn.lifespan.on', |
| | | 'uvicorn.lifespan.off', |
| | | 'uvicorn.protocols.websockets.auto', |
| | | 'uvicorn.protocols.websockets.websockets_impl', |
| | | 'uvicorn.protocols.http.auto', |
| | | 'uvicorn.protocols.http.h11_impl', |
| | | 'uvicorn.protocols.http.httptools_impl', |
| | | 'uvicorn.loops.auto', |
| | | 'uvicorn.loops.asyncio', |
| | | 'uvicorn.logging', |
| | | 'pymysql.converters', |
| | | 'pymysql.cursors', |
| | | 'pymysql.connections', |
| | | 'sqlalchemy.dialects.mysql.pymysql', |
| | | 'sqlalchemy.pool', |
| | | 'sqlalchemy.engine.default', |
| | | 'app.api.callback', |
| | | 'app.models.database', |
| | | 'app.workers.message_worker', |
| | | 'app.services.message_processor', |
| | | 'app.services.dify_client', |
| | | 'app.services.ecloud_client', |
| | | 'app.services.redis_queue', |
| | | 'app.utils.logger', |
| | | ]) |
| | | |
| | | # æé¤ä¸éè¦ç模å |
| | | excludes = [ |
| | | 'tkinter', |
| | | 'matplotlib', |
| | | 'numpy', |
| | | 'pandas', |
| | | 'scipy', |
| | | 'PIL', |
| | | 'IPython', |
| | | 'jupyter', |
| | | 'notebook', |
| | | 'pytest', |
| | | 'test', |
| | | 'tests', |
| | | ] |
| | | |
| | | block_cipher = None |
| | | |
| | | a = Analysis( |
| | | ['startup.py'], |
| | | pathex=[project_root], |
| | | binaries=[], |
| | | datas=datas, |
| | | hiddenimports=hiddenimports, |
| | | hookspath=[], |
| | | hooksconfig={}, |
| | | runtime_hooks=[], |
| | | excludes=excludes, |
| | | win_no_prefer_redirects=False, |
| | | win_private_assemblies=False, |
| | | cipher=block_cipher, |
| | | noarchive=False, |
| | | ) |
| | | |
| | | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) |
| | | |
| | | exe = EXE( |
| | | pyz, |
| | | a.scripts, |
| | | a.binaries, |
| | | a.zipfiles, |
| | | a.datas, |
| | | [], |
| | | name='ecloud_dify', |
| | | debug=False, |
| | | bootloader_ignore_signals=False, |
| | | strip=False, |
| | | upx=True, |
| | | upx_exclude=[], |
| | | runtime_tmpdir=None, |
| | | console=True, |
| | | disable_windowed_traceback=False, |
| | | argv_emulation=False, |
| | | target_arch=None, |
| | | codesign_identity=None, |
| | | entitlements_file=None, |
| | | icon=None, |
| | | ) |
New file |
| | |
| | | @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=å°Eäºç®¡å®¶æ¶æ¯è½¬åå°DifyAIå¹¶è¿åAIåççæå¡" |
| | | set "EXE_PATH=%CURRENT_DIR%ecloud_dify.exe" |
| | | |
| | | echo å½åç®å½: %CURRENT_DIR% |
| | | echo æå¡åç§°: %SERVICE_NAME% |
| | | echo 坿§è¡æä»¶: %EXE_PATH% |
| | | echo. |
| | | |
| | | :: æ£æ¥exeæä»¶æ¯å¦åå¨ |
| | | 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 æ£å¨å建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 æè
使ç¨Windowsæå¡ç®¡çå¨ (services.msc) è¿è¡ç®¡ç |
| | | echo. |
| | | |
| | | :: è¯¢é®æ¯å¦ç«å³å¯å¨æå¡ |
| | | set /p START_NOW="æ¯å¦ç«å³å¯å¨æå¡ï¼(Y/N): " |
| | | if /i "%START_NOW%"=="Y" ( |
| | | echo æ£å¨å¯å¨æå¡... |
| | | 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 |
| | |
| | | 2025-07-22 14:23:00 | INFO | __main__:<module>:94 - å¯å¨Eäºç®¡å®¶-DifyAIå¯¹æ¥æå¡ |
| | | 2025-07-22 14:24:44 | INFO | __main__:<module>:94 - å¯å¨Eäºç®¡å®¶-DifyAIå¯¹æ¥æå¡ |
| | | 2025-07-22 14:25:57 | INFO | __main__:<module>:94 - å¯å¨Eäºç®¡å®¶-DifyAIå¯¹æ¥æå¡ |
| | | 2025-07-22 14:37:50 | INFO | __main__:<module>:94 - å¯å¨Eäºç®¡å®¶-DifyAIå¯¹æ¥æå¡ |
| | | 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å¯¹æ¥æå¡ |
| | |
| | | from fastapi.middleware.cors import CORSMiddleware |
| | | from contextlib import asynccontextmanager |
| | | from loguru import logger |
| | | import time |
| | | from config import settings |
| | | from app.api.callback import router as callback_router |
| | | from app.models.database import create_tables |
| | |
| | | } |
| | | |
| | | |
| | | @app.get("/health") |
| | | async def health_check(): |
| | | """å¥åº·æ£æ¥æ¥å£""" |
| | | return { |
| | | "status": "healthy", |
| | | "message": "Eäºç®¡å®¶-DifyAIå¯¹æ¥æå¡è¿è¡æ£å¸¸", |
| | | "timestamp": int(time.time()), |
| | | } |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | # é
ç½®æ¥å¿ |
| | | logger.add( |
| | | settings.log_file, |
| | | rotation="1 day", |
| | | retention="30 days", |
| | | retention="7 days", |
| | | level=settings.log_level, |
| | | format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} - {message}", |
| | | ) |
| | |
| | | cryptography==45.0.5 |
| | | pytest==8.4.1 |
| | | httpx==0.28.1 |
| | | pyinstaller==6.3.0 |
| | | pywin32==306 |
New file |
| | |
| | | @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. |
| | | |
| | | :: æ£æ¥exeæä»¶æ¯å¦åå¨ |
| | | 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äºç®¡å®¶-DifyAIå¯¹æ¥æå¡... |
| | | echo æå¡å°å¨ç«¯å£ 7979 ä¸è¿è¡ |
| | | echo å¯ä»¥éè¿ http://localhost:7979 è®¿é®æå¡ |
| | | echo æ Ctrl+C 忢æå¡ |
| | | echo. |
| | | |
| | | :: å¯å¨æå¡ |
| | | "%EXE_PATH%" |
| | | |
| | | echo. |
| | | echo æå¡å·²åæ¢ |
| | | pause |
New file |
| | |
| | | """ |
| | | å¯å¨èæ¬ - å¤çé
ç½®æä»¶åç®å½åå§å |
| | | """ |
| | | |
| | | import os |
| | | import sys |
| | | import json |
| | | import shutil |
| | | from pathlib import Path |
| | | |
| | | |
| | | def get_exe_dir(): |
| | | """è·åexeæä»¶æå¨ç®å½""" |
| | | if getattr(sys, 'frozen', False): |
| | | # å¦ææ¯æå
åçexeæä»¶ |
| | | 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() |
| | | |
| | | # 设置工ä½ç®å½ä¸ºexeæå¨ç®å½ |
| | | 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) |
New file |
| | |
| | | @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 æ£å¨åæ¢æå¡... |
| | | sc stop "%SERVICE_NAME%" >nul 2>&1 |
| | | if %errorLevel% == 0 ( |
| | | echo æå¡å·²åæ¢ |
| | | ) else ( |
| | | echo æå¡å¯è½å·²ç»åæ¢æåæ¢å¤±è´¥ |
| | | ) |
| | | |
| | | :: çå¾
æå¡å®å
¨åæ¢ |
| | | echo çå¾
æå¡å®å
¨åæ¢... |
| | | timeout /t 5 /nobreak >nul |
| | | |
| | | :: å é¤æå¡ |
| | | echo æ£å¨å 餿å¡... |
| | | sc delete "%SERVICE_NAME%" |
| | | if %errorLevel% == 0 ( |
| | | echo æå¡å 餿åï¼ |
| | | ) else ( |
| | | echo éè¯¯ï¼æå¡å é¤å¤±è´¥ |
| | | pause |
| | | exit /b 1 |
| | | ) |
| | | ) else ( |
| | | echo æªæ¾å°æå¡ %SERVICE_NAME% |
| | | echo å¯è½æå¡å·²ç»è¢«å 餿仿ªå®è£
|
| | | ) |
| | | |
| | | echo. |
| | | echo æå¡å¸è½½å®æï¼ |
| | | echo. |
| | | pause |