mistpe commited on
Commit
8e12f48
·
verified ·
1 Parent(s): 53ea951

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1004
app.py DELETED
@@ -1,1004 +0,0 @@
1
- from flask import Flask, request, make_response
2
- import hashlib
3
- import time
4
- import xml.etree.ElementTree as ET
5
- import os
6
- import json
7
- from openai import OpenAI
8
- from dotenv import load_dotenv
9
- from markdown import markdown
10
- import re
11
- import threading
12
- import logging
13
- from datetime import datetime
14
- import asyncio
15
- from concurrent.futures import ThreadPoolExecutor
16
- import queue
17
- import uuid
18
- import base64
19
- from Crypto.Cipher import AES
20
- import struct
21
- import random
22
- import string
23
- import requests
24
-
25
- logging.basicConfig(
26
- level=logging.INFO,
27
- format='%(asctime)s - %(levelname)s - %(message)s',
28
- handlers=[
29
- logging.FileHandler('wechat_service.log'),
30
- logging.StreamHandler()
31
- ]
32
- )
33
-
34
- load_dotenv()
35
-
36
- app = Flask(__name__)
37
-
38
- # Configuration
39
- TOKEN = os.getenv('TOKEN')
40
- ENCODING_AES_KEY = os.getenv('ENCODING_AES_KEY')
41
- APPID = os.getenv('APPID')
42
- APPSECRET = os.getenv('APPSECRET') # 新增 AppSecret
43
- API_KEY = os.getenv("API_KEY")
44
- BASE_URL = os.getenv("OPENAI_BASE_URL")
45
- IMAGE_MODEL_URL = os.getenv("IMAGE_MODEL_URL")
46
- IMAGE_MODEL_KEY = os.getenv("IMAGE_MODEL_KEY")
47
-
48
- client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
49
- executor = ThreadPoolExecutor(max_workers=10)
50
-
51
-
52
- # Define tools for image generation
53
- TOOLS = [
54
- {
55
- "type": "function",
56
- "function": {
57
- "name": "generate_image",
58
- "description": "Generate an image based on text description",
59
- "parameters": {
60
- "type": "object",
61
- "properties": {
62
- "prompt": {
63
- "type": "string",
64
- "description": "The description of the image to generate"
65
- }
66
- },
67
- "required": ["prompt"]
68
- }
69
- }
70
- }
71
- ]
72
- class AccessTokenManager:
73
- def __init__(self):
74
- self._access_token = None
75
- self._expires_at = 0
76
- self._lock = threading.Lock()
77
-
78
- def get_token(self):
79
- with self._lock:
80
- now = time.time()
81
- # 提前5分钟刷新token,确保在调用时token都是有效的
82
- if self._access_token and now < (self._expires_at - 300):
83
- return self._access_token
84
-
85
- try:
86
- url = "https://api.weixin.qq.com/cgi-bin/token"
87
- params = {
88
- "grant_type": "client_credential",
89
- "appid": APPID,
90
- "secret": APPSECRET
91
- }
92
-
93
- logging.info("开始获取新的access_token")
94
- response = requests.get(url, params=params)
95
- response.raise_for_status()
96
- result = response.json()
97
-
98
- if "access_token" not in result:
99
- error_msg = f"获取access_token失败: {result}"
100
- logging.error(error_msg)
101
- raise ValueError(error_msg)
102
-
103
- self._access_token = result["access_token"]
104
- self._expires_at = now + result["expires_in"]
105
- logging.info("成功获取新的access_token")
106
-
107
- return self._access_token
108
-
109
- except Exception as e:
110
- error_msg = f"获取access_token时发生错误: {str(e)}"
111
- logging.error(error_msg)
112
- raise
113
-
114
- def refresh_token(self):
115
- with self._lock:
116
- self._access_token = None
117
- return self.get_token()
118
-
119
- class WeChatCrypto:
120
- def __init__(self, key, app_id):
121
- self.key = base64.b64decode(key + '=')
122
- self.app_id = app_id
123
-
124
- def encrypt(self, text):
125
- random_str = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
126
- text_bytes = text.encode('utf-8')
127
- msg_len = struct.pack('>I', len(text_bytes))
128
- message = random_str.encode('utf-8') + msg_len + text_bytes + self.app_id.encode('utf-8')
129
- pad_len = 32 - (len(message) % 32)
130
- message += chr(pad_len).encode('utf-8') * pad_len
131
- cipher = AES.new(self.key, AES.MODE_CBC, self.key[:16])
132
- encrypted = cipher.encrypt(message)
133
- return base64.b64encode(encrypted).decode('utf-8')
134
-
135
- def decrypt(self, encrypted_text):
136
- encrypted_data = base64.b64decode(encrypted_text)
137
- cipher = AES.new(self.key, AES.MODE_CBC, self.key[:16])
138
- decrypted = cipher.decrypt(encrypted_data)
139
- pad_len = decrypted[-1]
140
- if not isinstance(pad_len, int):
141
- pad_len = ord(pad_len)
142
- content = decrypted[16:-pad_len]
143
- msg_len = struct.unpack('>I', content[:4])[0]
144
- xml_content = content[4:msg_len + 4].decode('utf-8')
145
- app_id = content[msg_len + 4:].decode('utf-8')
146
- if app_id != self.app_id:
147
- raise ValueError('Invalid AppID')
148
- return xml_content
149
-
150
- class AsyncResponse:
151
- def __init__(self):
152
- self.status = "processing"
153
- self.result = None
154
- self.error = None
155
- self.create_time = time.time()
156
- self.timeout = 3600
157
- self.response_type = "text" # Can be "text" or "image"
158
- self.media_id = None # For image responses
159
-
160
- def is_expired(self):
161
- return time.time() - self.create_time > self.timeout
162
-
163
- class UserSession:
164
- def __init__(self):
165
- self.messages = [{"role": "system", "content": "你是HXIAO公众号的智能助手,这一个用来分享与学习人工智能的公众号,我们的目标是专注AI应用的简单研究与实践。致力于分享切实可行的技术方案,希望让复杂的技术变得简单易懂。也喜欢用通俗的语言来解释专业概念,让技术真正服务于每个学习者"}]
166
- self.pending_parts = []
167
- self.last_active = time.time()
168
- self.current_task = None
169
- self.response_queue = {}
170
- self.session_timeout = 3600
171
-
172
- def is_expired(self):
173
- return time.time() - self.last_active > self.session_timeout
174
-
175
- def cleanup_expired_tasks(self):
176
- expired_tasks = [
177
- task_id for task_id, response in self.response_queue.items()
178
- if response.is_expired()
179
- ]
180
- for task_id in expired_tasks:
181
- del self.response_queue[task_id]
182
- if self.current_task == task_id:
183
- self.current_task = None
184
-
185
- class SessionManager:
186
- def __init__(self):
187
- self.sessions = {}
188
- self._lock = threading.Lock()
189
- self.crypto = WeChatCrypto(ENCODING_AES_KEY, APPID)
190
-
191
- def get_session(self, user_id):
192
- with self._lock:
193
- current_time = time.time()
194
- if user_id in self.sessions:
195
- session = self.sessions[user_id]
196
- if session.is_expired():
197
- session = UserSession()
198
- else:
199
- session.cleanup_expired_tasks()
200
- else:
201
- session = UserSession()
202
- session.last_active = current_time
203
- self.sessions[user_id] = session
204
- return session
205
-
206
- def clear_session(self, user_id):
207
- with self._lock:
208
- if user_id in self.sessions:
209
- self.sessions[user_id] = UserSession()
210
-
211
- def cleanup_expired_sessions(self):
212
- with self._lock:
213
- current_time = time.time()
214
- expired_users = [
215
- user_id for user_id, session in self.sessions.items()
216
- if session.is_expired()
217
- ]
218
- for user_id in expired_users:
219
- del self.sessions[user_id]
220
- logging.info(f"已清理过期会话: {user_id}")
221
-
222
- def convert_markdown_to_wechat(md_text):
223
- if not md_text:
224
- return md_text
225
-
226
- md_text = re.sub(r'^# (.*?)$', r'【标题】\1', md_text, flags=re.MULTILINE)
227
- md_text = re.sub(r'^## (.*?)$', r'【子标题】\1', md_text, flags=re.MULTILINE)
228
- md_text = re.sub(r'^### (.*?)$', r'【小标题】\1', md_text, flags=re.MULTILINE)
229
- md_text = re.sub(r'\*\*(.*?)\*\*', r'『\1』', md_text)
230
- md_text = re.sub(r'\*(.*?)\*', r'「\1」', md_text)
231
- md_text = re.sub(r'`(.*?)`', r'「\1」', md_text)
232
- md_text = re.sub(r'^\- ', '• ', md_text, flags=re.MULTILINE)
233
- md_text = re.sub(r'^\d\. ', '○ ', md_text, flags=re.MULTILINE)
234
- md_text = re.sub(r'```[\w]*\n(.*?)```', r'【代码开始】\n\1\n【代码结束】', md_text, flags=re.DOTALL)
235
- md_text = re.sub(r'^> (.*?)$', r'▎\1', md_text, flags=re.MULTILINE)
236
- md_text = re.sub(r'^-{3,}$', r'—————————', md_text, flags=re.MULTILINE)
237
- md_text = re.sub(r'\[(.*?)\]\((.*?)\)', r'\1(\2)', md_text)
238
- md_text = re.sub(r'\n{3,}', '\n\n', md_text)
239
-
240
- return md_text
241
-
242
- def verify_signature(signature, timestamp, nonce, token):
243
- items = [token, timestamp, nonce]
244
- items.sort()
245
- temp_str = ''.join(items)
246
- hash_sha1 = hashlib.sha1(temp_str.encode('utf-8')).hexdigest()
247
- return hash_sha1 == signature
248
-
249
- def verify_msg_signature(msg_signature, timestamp, nonce, token, encrypt_msg):
250
- """
251
- 验证消息签名
252
- Args:
253
- msg_signature: 消息签名
254
- timestamp: 时间戳
255
- nonce: 随机数
256
- token: 验证令牌
257
- encrypt_msg: 加密的消息内容
258
- Returns:
259
- bool: 签名是否有效
260
- """
261
- items = [token, timestamp, nonce, encrypt_msg]
262
- items.sort()
263
- temp_str = ''.join(items)
264
- hash_sha1 = hashlib.sha1(temp_str.encode('utf-8')).hexdigest()
265
- return hash_sha1 == msg_signature
266
-
267
-
268
- def parse_xml_message(xml_content):
269
- """
270
- 解析微信XML消息,支持文本和图片消息类型
271
- """
272
- root = ET.fromstring(xml_content)
273
- message = {
274
- 'from_user': root.find('FromUserName').text,
275
- 'to_user': root.find('ToUserName').text,
276
- 'create_time': root.find('CreateTime').text,
277
- 'msg_type': root.find('MsgType').text,
278
- 'msg_id': root.find('MsgId').text if root.find('MsgId') is not None else '',
279
- 'msg_data_id': root.find('MsgDataId').text if root.find('MsgDataId') is not None else '',
280
- 'idx': root.find('Idx').text if root.find('Idx') is not None else ''
281
- }
282
-
283
- if message['msg_type'] == 'text':
284
- message['content'] = root.find('Content').text if root.find('Content') is not None else ''
285
- elif message['msg_type'] == 'image':
286
- message['pic_url'] = root.find('PicUrl').text
287
- message['media_id'] = root.find('MediaId').text
288
-
289
- return message
290
-
291
- def get_image_content(media_id):
292
- """
293
- 通过微信接口获取图片内容
294
- """
295
- try:
296
- access_token = token_manager.get_token()
297
- url = f'https://api.weixin.qq.com/cgi-bin/media/get?access_token={access_token}&media_id={media_id}'
298
-
299
- logging.info(f"开始下载图片,media_id: {media_id}")
300
- response = requests.get(url)
301
-
302
- if response.headers.get('Content-Type') == 'text/plain':
303
- # 如果返回JSON错误信息
304
- error_info = response.json()
305
- if error_info.get('errcode') == 40001:
306
- # access_token过期,刷新后重试
307
- logging.info("access_token已过期,正在刷新并重试")
308
- access_token = token_manager.refresh_token()
309
- url = f'https://api.weixin.qq.com/cgi-bin/media/get?access_token={access_token}&media_id={media_id}'
310
- response = requests.get(url)
311
-
312
- response.raise_for_status()
313
- return response.content
314
-
315
- except Exception as e:
316
- logging.error(f"获取图片内容失败: {str(e)}")
317
- raise
318
-
319
-
320
- def generate_response_xml(to_user, from_user, content, response_type='text', media_id=None, encrypt_type='aes'):
321
- timestamp = str(int(time.time()))
322
- nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
323
-
324
- if response_type == 'image' and media_id:
325
- xml_content = f'''
326
- <xml>
327
- <ToUserName><![CDATA[{to_user}]]></ToUserName>
328
- <FromUserName><![CDATA[{from_user}]]></FromUserName>
329
- <CreateTime>{timestamp}</CreateTime>
330
- <MsgType><![CDATA[image]]></MsgType>
331
- <Image>
332
- <MediaId><![CDATA[{media_id}]]></MediaId>
333
- </Image>
334
- </xml>
335
- '''
336
- else:
337
- formatted_content = convert_markdown_to_wechat(content)
338
- xml_content = f'''
339
- <xml>
340
- <ToUserName><![CDATA[{to_user}]]></ToUserName>
341
- <FromUserName><![CDATA[{from_user}]]></FromUserName>
342
- <CreateTime>{timestamp}</CreateTime>
343
- <MsgType><![CDATA[text]]></MsgType>
344
- <Content><![CDATA[{formatted_content}]]></Content>
345
- </xml>
346
- '''
347
-
348
- if encrypt_type == 'aes':
349
- encrypted = session_manager.crypto.encrypt(xml_content)
350
- signature_list = [TOKEN, timestamp, nonce, encrypted]
351
- signature_list.sort()
352
- msg_signature = hashlib.sha1(''.join(signature_list).encode('utf-8')).hexdigest()
353
-
354
- response_xml = f'''
355
- <xml>
356
- <Encrypt><![CDATA[{encrypted}]]></Encrypt>
357
- <MsgSignature><![CDATA[{msg_signature}]]></MsgSignature>
358
- <TimeStamp>{timestamp}</TimeStamp>
359
- <Nonce><![CDATA[{nonce}]]></Nonce>
360
- </xml>
361
- '''
362
- else:
363
- response_xml = xml_content
364
-
365
- response = make_response(response_xml)
366
- response.content_type = 'application/xml'
367
- return response
368
-
369
- # 创建全局的token管理器实例
370
- token_manager = AccessTokenManager()
371
- def upload_image_to_wechat(image_data):
372
- """上传图片到微信服务器并获取media_id"""
373
- try:
374
- access_token = token_manager.get_token()
375
- upload_url = f'https://api.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type=image'
376
- files = {'media': ('image.jpg', image_data, 'image/jpeg')}
377
-
378
- logging.info("开始上传图片到微信服务器")
379
- response = requests.post(upload_url, files=files)
380
- response.raise_for_status()
381
- result = response.json()
382
-
383
- if 'media_id' not in result:
384
- if 'errcode' in result and result['errcode'] == 40001:
385
- # access_token 可能过期,尝试刷新并重试
386
- logging.info("access_token已过期,正在刷新并重试")
387
- access_token = token_manager.refresh_token()
388
- upload_url = f'https://api.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type=image'
389
- response = requests.post(upload_url, files=files)
390
- response.raise_for_status()
391
- result = response.json()
392
-
393
- if 'media_id' not in result:
394
- error_msg = f"上传图片失败: {result}"
395
- logging.error(error_msg)
396
- raise ValueError(error_msg)
397
-
398
- logging.info(f"图片上传成功,获取到media_id")
399
- return result['media_id']
400
-
401
- except Exception as e:
402
- error_msg = f"上传图片过程中发生错误: {str(e)}"
403
- logging.error(error_msg)
404
- raise
405
-
406
- # def process_long_running_task(messages, message_type='text', image_data=None):
407
- # """
408
- # 处理长时间运行的任务,支持文本对话和图片识别
409
- # Args:
410
- # messages: 消息历史记录列表
411
- # message_type: 消息类型,'text'或'image'
412
- # image_data: 图片相关数据,包含media_id等信息
413
- # Returns:
414
- # dict: 包含处理结果的字典
415
- # """
416
- # try:
417
- # logging.info(f"开始调用AI服务,消息类型: {message_type}")
418
-
419
- # if message_type == 'image':
420
- # try:
421
- # # 获取图片内容
422
- # image_content = get_image_content(image_data['media_id'])
423
- # # 将图片内容转换为base64
424
- # image_base64 = base64.b64encode(image_content).decode('utf-8')
425
-
426
- # # 构建图像识别请求
427
- # image_messages = [
428
- # {
429
- # "role": "user",
430
- # "content": [
431
- # {"type": "text", "text": "请详细描述这张图片中的内容,包括主要对象、场景、活动等关键信息"},
432
- # {
433
- # "type": "image_url",
434
- # "image_url": {
435
- # "url": f"data:image/jpeg;base64,{image_base64}"
436
- # }
437
- # }
438
- # ]
439
- # }
440
- # ]
441
-
442
- # logging.info("开始调用图像识别模型")
443
- # image_response = client.chat.completions.create(
444
- # model="gpt-4o-mini",
445
- # messages=image_messages,
446
- # max_tokens=300,
447
- # timeout=60
448
- # )
449
- # logging.info("图像识别完成")
450
-
451
- # # 检查响应是否成功
452
- # if not image_response.choices:
453
- # raise Exception("图像识别服务未返回有效结果")
454
-
455
- # return {
456
- # "type": "text",
457
- # "content": image_response.choices[0].message.content
458
- # }
459
-
460
- # except requests.exceptions.RequestException as e:
461
- # logging.error(f"获取或处理图片时发生网络错误: {str(e)}")
462
- # raise
463
- # except Exception as e:
464
- # logging.error(f"图像识别过程中发生错误: {str(e)}")
465
- # raise
466
-
467
- # else:
468
- # # 处理文本消息
469
- # try:
470
- # logging.info("开始处理文本消息")
471
- # response = client.chat.completions.create(
472
- # model="o3-mini",
473
- # messages=messages,
474
- # tools=TOOLS,
475
- # tool_choice="auto",
476
- # timeout=60
477
- # )
478
-
479
- # # 检查是否需要生成图片
480
- # if response.choices[0].message.tool_calls:
481
- # tool_call = response.choices[0].message.tool_calls[0]
482
- # if tool_call.function.name == "generate_image":
483
- # try:
484
- # logging.info("检测到图片生成请求")
485
- # args = json.loads(tool_call.function.arguments)
486
-
487
- # # 调用图片生成服务
488
- # image_generation_response = requests.post(
489
- # IMAGE_MODEL_URL,
490
- # headers={
491
- # 'Content-Type': 'application/json',
492
- # 'Authorization': f'Bearer {IMAGE_MODEL_KEY}'
493
- # },
494
- # json={
495
- # "model": "grok-2-imageGen",
496
- # "messages": [{
497
- # "role": "user",
498
- # "content": args['prompt']
499
- # }]
500
- # },
501
- # timeout=60
502
- # )
503
- # image_generation_response.raise_for_status()
504
- # generation_result = image_generation_response.json()
505
-
506
- # if 'choices' not in generation_result or not generation_result['choices']:
507
- # raise Exception("图片生成服务未返回有效结果")
508
-
509
- # # 从markdown格式中提取URL
510
- # markdown_content = generation_result['choices'][0]['message']['content']
511
- # image_url_match = re.search(r'\!\[image\]\((.*?)\)', markdown_content)
512
- # if not image_url_match:
513
- # raise Exception("无法从响应中提取图片URL")
514
-
515
- # image_url = image_url_match.group(1)
516
-
517
- # # 下载生成的图片
518
- # img_response = requests.get(image_url, timeout=30)
519
- # img_response.raise_for_status()
520
-
521
- # # 上传图片到微信服务器
522
- # media_id = upload_image_to_wechat(img_response.content)
523
-
524
- # return {
525
- # "type": "image",
526
- # "media_id": media_id
527
- # }
528
-
529
- # except requests.exceptions.RequestException as e:
530
- # logging.error(f"图片生成过程中发生网络错误: {str(e)}")
531
- # raise
532
- # except Exception as e:
533
- # logging.error(f"图片生成过程中发生错误: {str(e)}")
534
- # raise
535
-
536
- # # 返回文本响应
537
- # return {
538
- # "type": "text",
539
- # "content": response.choices[0].message.content
540
- # }
541
-
542
- # except requests.exceptions.RequestException as e:
543
- # logging.error(f"处理文本消息时发生网络错误: {str(e)}")
544
- # raise
545
- # except Exception as e:
546
- # logging.error(f"处理文本消息时发生错误: {str(e)}")
547
- # raise
548
-
549
- # except Exception as e:
550
- # logging.error(f"API调用错误: {str(e)}")
551
- # raise
552
- def process_long_running_task(messages, message_type='text', image_data=None):
553
- """
554
- 处理长时间运行的任务,支持文本对话和图片识别
555
- """
556
- try:
557
- logging.info(f"开始调用AI服务,消息类型: {message_type}")
558
-
559
- if message_type == 'image':
560
- # 图片识别逻辑保持不变
561
- try:
562
- image_content = get_image_content(image_data['media_id'])
563
- image_base64 = base64.b64encode(image_content).decode('utf-8')
564
-
565
- image_messages = [
566
- {
567
- "role": "user",
568
- "content": [
569
- {"type": "text", "text": "请详细描述这张图片中的内容,包括主要对象、场景、活动等关键信息"},
570
- {
571
- "type": "image_url",
572
- "image_url": {
573
- "url": f"data:image/jpeg;base64,{image_base64}"
574
- }
575
- }
576
- ]
577
- }
578
- ]
579
-
580
- logging.info("开始调用图像识别模型")
581
- image_response = client.chat.completions.create(
582
- model="gpt-4.1-mini",
583
- messages=image_messages,
584
- max_tokens=300,
585
- timeout=60
586
- )
587
- logging.info("图像识别完成")
588
-
589
- if not image_response.choices:
590
- raise Exception("图像识别服务未返回有效结果")
591
-
592
- return {
593
- "type": "text",
594
- "content": image_response.choices[0].message.content
595
- }
596
-
597
- except Exception as e:
598
- logging.error(f"图像识别过程中发生错误: {str(e)}")
599
- raise
600
-
601
- else:
602
- # 处理文本消息
603
- try:
604
- logging.info("开始处理文本消息")
605
- response = client.chat.completions.create(
606
- model="gpt-4.1-mini",
607
- messages=messages,
608
- tools=TOOLS,
609
- tool_choice="auto",
610
- timeout=60
611
- )
612
-
613
- # 检查是否需要生成图片
614
- if response.choices[0].message.tool_calls:
615
- tool_call = response.choices[0].message.tool_calls[0]
616
- if tool_call.function.name == "generate_image":
617
- try:
618
- logging.info("检测到图片生成请求")
619
- args = json.loads(tool_call.function.arguments)
620
-
621
- # 使用新的DALL-E 3 API进行图片生成
622
- image_generation_response = requests.post(
623
- "https://api1.oaipro.com/v1/images/generations",
624
- headers={
625
- 'Content-Type': 'application/json',
626
- 'Authorization': f'Bearer {API_KEY}'
627
- },
628
- json={
629
- "model": "dall-e-3",
630
- "prompt": args['prompt'],
631
- "n": 1,
632
- "size": "1024x1024"
633
- },
634
- timeout=60
635
- )
636
- image_generation_response.raise_for_status()
637
- generation_result = image_generation_response.json()
638
-
639
- if 'data' not in generation_result or not generation_result['data']:
640
- raise Exception("图片生成服务未返回有效结果")
641
-
642
- # 获取生成的图片URL
643
- image_url = generation_result['data'][0]['url']
644
-
645
- # 下载生成的图片
646
- img_response = requests.get(image_url, timeout=30)
647
- img_response.raise_for_status()
648
-
649
- # 上传图片到微信服务器
650
- media_id = upload_image_to_wechat(img_response.content)
651
-
652
- return {
653
- "type": "image",
654
- "media_id": media_id
655
- }
656
-
657
- except requests.exceptions.RequestException as e:
658
- logging.error(f"图片生成过程中发生网络错误: {str(e)}")
659
- raise
660
- except Exception as e:
661
- logging.error(f"图片生成过程中发生错误: {str(e)}")
662
- raise
663
-
664
- # 返回文本响应
665
- return {
666
- "type": "text",
667
- "content": response.choices[0].message.content
668
- }
669
-
670
- except requests.exceptions.RequestException as e:
671
- logging.error(f"处理文本消息时发生网络错误: {str(e)}")
672
- raise
673
- except Exception as e:
674
- logging.error(f"处理文本消息时发生错误: {str(e)}")
675
- raise
676
-
677
- except Exception as e:
678
- logging.error(f"API调用错误: {str(e)}")
679
- raise
680
-
681
- def handle_async_task(session, task_id, messages=None, message_type='text', message_data=None):
682
- """
683
- 处理异步任务,支持文本对话和图片识别
684
- """
685
- try:
686
- logging.info(f"开始处理异步任务: {task_id}, 类型: {message_type}")
687
-
688
- if task_id not in session.response_queue:
689
- return
690
-
691
- if message_type == 'image':
692
- result = process_long_running_task(None, 'image', message_data)
693
- else:
694
- result = process_long_running_task(messages)
695
-
696
- if task_id in session.response_queue and not session.response_queue[task_id].is_expired():
697
- session.response_queue[task_id].status = "completed"
698
- session.response_queue[task_id].response_type = result.get("type", "text")
699
-
700
- if result["type"] == "image":
701
- session.response_queue[task_id].media_id = result["media_id"]
702
- session.response_queue[task_id].result = None
703
- else:
704
- session.response_queue[task_id].result = result["content"]
705
-
706
- if messages and result["type"] == "text":
707
- messages.append({"role": "assistant", "content": result["content"]})
708
-
709
- except Exception as e:
710
- logging.error(f"异步任务处理失败: {str(e)}")
711
- if task_id in session.response_queue:
712
- session.response_queue[task_id].status = "failed"
713
- session.response_queue[task_id].error = str(e)
714
-
715
-
716
- def generate_initial_response():
717
- return "您的请求正在处理中,请回复'查询'获取结果(生图需要时间)"
718
-
719
- def split_message(message, max_length=500):
720
- """
721
- 将长消息分割成多个部分
722
- Args:
723
- message: 需要分割的消息
724
- max_length: 每部分的最大长度
725
- Returns:
726
- list: 分割后的消息部分列表
727
- """
728
- return [message[i:i+max_length] for i in range(0, len(message), max_length)]
729
-
730
- def append_status_message(content, has_pending_parts=False, is_processing=False):
731
- """
732
- 添加状态消息到响应内容
733
- Args:
734
- content: 原始内容
735
- has_pending_parts: 是否有待发送的部分
736
- is_processing: 是否正在处理中
737
- Returns:
738
- str: 添加了状态信息的内容
739
- """
740
- if "您的请求正在处理中" in content:
741
- return content + "\n\n-------------------\n发送'新对话'开始新的对话"
742
-
743
- status_message = "\n\n-------------------"
744
- if is_processing:
745
- status_message += "\n请回复'查询'获取结果"
746
- elif has_pending_parts:
747
- status_message += "\n当前消息已截断,发送'继续'查看后续内容"
748
- status_message += "\n发送'新对话'开始新的对话"
749
- return content + status_message
750
-
751
- session_manager = SessionManager()
752
-
753
- @app.route('/api/wx', methods=['GET', 'POST'])
754
- def wechatai():
755
- """
756
- 处理微信公众号请求的主要路由函数
757
- """
758
- if request.method == 'GET':
759
- signature = request.args.get('signature')
760
- timestamp = request.args.get('timestamp')
761
- nonce = request.args.get('nonce')
762
- echostr = request.args.get('echostr')
763
-
764
- if verify_signature(signature, timestamp, nonce, TOKEN):
765
- return echostr
766
- return 'error', 403
767
-
768
- try:
769
- encrypt_type = request.args.get('encrypt_type', '')
770
- current_time = int(time.time())
771
-
772
- if encrypt_type == 'aes':
773
- msg_signature = request.args.get('msg_signature')
774
- timestamp = request.args.get('timestamp')
775
- nonce = request.args.get('nonce')
776
-
777
- # 解析加密消息
778
- xml_tree = ET.fromstring(request.data)
779
- encrypted_text = xml_tree.find('Encrypt').text
780
-
781
- # 验证消息签名
782
- if not verify_msg_signature(msg_signature, timestamp, nonce, TOKEN, encrypted_text):
783
- logging.error("消息签名验证失败")
784
- return 'Invalid signature', 403
785
-
786
- try:
787
- # 解密消息
788
- decrypted_xml = session_manager.crypto.decrypt(encrypted_text)
789
- message_data = parse_xml_message(decrypted_xml)
790
- except Exception as e:
791
- logging.error(f"消息解密失败: {str(e)}")
792
- return 'Decryption failed', 403
793
- else:
794
- # 处理未加密的消息
795
- try:
796
- message_data = parse_xml_message(request.data)
797
- except Exception as e:
798
- logging.error(f"解析XML消息失败: {str(e)}")
799
- return 'Invalid XML', 400
800
-
801
- from_user = message_data['from_user']
802
- to_user = message_data['to_user']
803
- msg_type = message_data['msg_type']
804
-
805
- logging.info(f"收到用户({from_user})消息类型: {msg_type}")
806
-
807
- # 获取或创建用户会话
808
- try:
809
- session = session_manager.get_session(from_user)
810
- except Exception as e:
811
- logging.error(f"获取用户会话失败: {str(e)}")
812
- return generate_response_xml(
813
- from_user,
814
- to_user,
815
- append_status_message('系统错误,请稍后重试。'),
816
- encrypt_type=encrypt_type
817
- )
818
-
819
- if msg_type == 'image':
820
- try:
821
- logging.info(f"收到图片消息: MediaId={message_data.get('media_id')}")
822
-
823
- # 创建新的异步任务
824
- task_id = str(uuid.uuid4())
825
- session.current_task = task_id
826
- session.response_queue[task_id] = AsyncResponse()
827
-
828
- # 启动异步图片识别任务
829
- executor.submit(
830
- handle_async_task,
831
- session,
832
- task_id,
833
- None,
834
- 'image',
835
- message_data
836
- )
837
-
838
- return generate_response_xml(
839
- from_user,
840
- to_user,
841
- append_status_message("正在分析图片,请稍候...\n回复'查询'获取分析结果", is_processing=True),
842
- encrypt_type=encrypt_type
843
- )
844
- except Exception as e:
845
- logging.error(f"处理图片消息时发生错误: {str(e)}")
846
- return generate_response_xml(
847
- from_user,
848
- to_user,
849
- append_status_message('处理图片时出现错误,请稍后重试。'),
850
- encrypt_type=encrypt_type
851
- )
852
- else:
853
- # 处理文本消息
854
- try:
855
- user_content = message_data['content'].strip()
856
-
857
- # 处理特殊命令
858
- if user_content == '新对话':
859
- session_manager.clear_session(from_user)
860
- return generate_response_xml(
861
- from_user,
862
- to_user,
863
- append_status_message('已开始新的对话。请描述您的问题。'),
864
- encrypt_type=encrypt_type
865
- )
866
-
867
- if user_content == '继续':
868
- if session.pending_parts:
869
- next_part = session.pending_parts.pop(0)
870
- has_more = bool(session.pending_parts)
871
- return generate_response_xml(
872
- from_user,
873
- to_user,
874
- append_status_message(next_part, has_more),
875
- encrypt_type=encrypt_type
876
- )
877
- return generate_response_xml(
878
- from_user,
879
- to_user,
880
- append_status_message('没有更多内容了。请继续您的问题。'),
881
- encrypt_type=encrypt_type
882
- )
883
-
884
- if user_content == '查询':
885
- if session.current_task:
886
- task_response = session.response_queue.get(session.current_task)
887
- if task_response:
888
- if task_response.is_expired():
889
- del session.response_queue[session.current_task]
890
- session.current_task = None
891
- return generate_response_xml(
892
- from_user,
893
- to_user,
894
- append_status_message('请求已过期,请重新提问。'),
895
- encrypt_type=encrypt_type
896
- )
897
-
898
- if task_response.status == "completed":
899
- if task_response.response_type == "image":
900
- logging.info("返回图片响应")
901
- del session.response_queue[session.current_task]
902
- session.current_task = None
903
- return generate_response_xml(
904
- from_user,
905
- to_user,
906
- "",
907
- response_type="image",
908
- media_id=task_response.media_id,
909
- encrypt_type=encrypt_type
910
- )
911
- else:
912
- response = task_response.result
913
- del session.response_queue[session.current_task]
914
- session.current_task = None
915
-
916
- if len(response) > 500:
917
- parts = split_message(response)
918
- first_part = parts.pop(0)
919
- session.pending_parts = parts
920
- return generate_response_xml(
921
- from_user,
922
- to_user,
923
- append_status_message(first_part, True),
924
- encrypt_type=encrypt_type
925
- )
926
- return generate_response_xml(
927
- from_user,
928
- to_user,
929
- append_status_message(response),
930
- encrypt_type=encrypt_type
931
- )
932
- elif task_response.status == "failed":
933
- error_message = '处理过程中出现错误,请重新提问。'
934
- del session.response_queue[session.current_task]
935
- session.current_task = None
936
- return generate_response_xml(
937
- from_user,
938
- to_user,
939
- append_status_message(error_message),
940
- encrypt_type=encrypt_type
941
- )
942
- else:
943
- return generate_response_xml(
944
- from_user,
945
- to_user,
946
- append_status_message('正在处理中,请稍后再次查询。', is_processing=True),
947
- encrypt_type=encrypt_type
948
- )
949
- return generate_response_xml(
950
- from_user,
951
- to_user,
952
- append_status_message('没有正在处理的请求。'),
953
- encrypt_type=encrypt_type
954
- )
955
-
956
- # 处理普通文本消息
957
- session.messages.append({"role": "user", "content": user_content})
958
-
959
- # 创建新的异步任务
960
- task_id = str(uuid.uuid4())
961
- session.current_task = task_id
962
- session.response_queue[task_id] = AsyncResponse()
963
-
964
- # 启动异步处理
965
- executor.submit(handle_async_task, session, task_id, session.messages.copy())
966
-
967
- return generate_response_xml(
968
- from_user,
969
- to_user,
970
- append_status_message(generate_initial_response(), is_processing=True),
971
- encrypt_type=encrypt_type
972
- )
973
-
974
- except Exception as e:
975
- logging.error(f"处理文本消息时发生错误: {str(e)}")
976
- return generate_response_xml(
977
- from_user,
978
- to_user,
979
- append_status_message('处理消息时出现错误,请稍后重试。'),
980
- encrypt_type=encrypt_type
981
- )
982
-
983
- except Exception as e:
984
- logging.error(f"处理请求时出错: {str(e)}")
985
- return generate_response_xml(
986
- message_data['from_user'] if 'message_data' in locals() else 'unknown',
987
- message_data['to_user'] if 'message_data' in locals() else 'unknown',
988
- append_status_message('抱歉,系统暂时出现问题,请稍后重试。'),
989
- encrypt_type if 'encrypt_type' in locals() else ''
990
- )
991
-
992
- def cleanup_sessions():
993
- while True:
994
- time.sleep(3600) # 每小时清理一次
995
- try:
996
- session_manager.cleanup_expired_sessions()
997
- except Exception as e:
998
- logging.error(f"清理会话时出错: {str(e)}")
999
-
1000
- if __name__ == '__main__':
1001
- cleanup_thread = threading.Thread(target=cleanup_sessions, daemon=True)
1002
- cleanup_thread.start()
1003
-
1004
- app.run(host='0.0.0.0', port=7860, debug=True)