主要修改在asr-monitor-test 修改小程序手机号码登录 小程序的TTS生成(查一查、AI) 增加和支付相关的功能

This commit is contained in:
qcloud
2025-07-10 22:04:44 +08:00
parent 0665eb2c2d
commit 74899acab9
23 changed files with 4467 additions and 459 deletions

View File

@@ -141,15 +141,35 @@ def upload_file(tenant_id,mesum_id):
#logging.info(f"mesumid={mesum_id} {joined_string}") #logging.info(f"mesumid={mesum_id} {joined_string}")
prompt = (f"你是一名图片识别和理解助手" prompt1 = (f"你是一名图片识别和理解助手"
f"任务是先识别图片中文字,然后理解文字中包含的内容,分析哪一项可以作为识别出文字的标题," f"任务是先识别图片中文字,然后理解文字中包含的内容,分析哪一项可以作为识别出文字的标题,"
f"你的回答有3个结果第一个结果匹配出的结果,JSON键值为antique" f"你的回答有3个结果第一个结果匹配出的结果,JSON键值为antique"
f"从下面的候选项:{antiques_selected}进行匹配,每一个候选项中间以','分割,如果没有任何匹配则结果为'',以免误触发讲解,匹配成功则输出匹配出的内容" f"从下面的候选项:{antiques_selected}进行匹配,每一个候选项中间以';'分割,如果没有任何匹配则结果为'',以免误触发讲解,匹配成功则输出匹配出的内容"
f",第二个结果是原始识别的所有文字,json 键值为text" f",第二个结果是原始识别的所有文字,json 键值为text"
f"第三个结果是识别出文字与匹配项列表中元素的匹配度范围从0-1,1表示100%匹配,0表示完全不匹配,JSON键值为match_score," f"第三个结果是识别出文字与匹配项列表中元素的匹配度范围从0-1,1表示100%匹配,0表示完全不匹配,JSON键值为match_score,"
"3个结果输出以{ }的json格式给出匹配出文物、事件、人物的结果键值为antique" "3个结果输出以{ }的json格式给出匹配出文物、事件、人物的结果键值为antique"
f"原始数据的键值为text输出是1个完整的JSON数据不要有多余的前置和后置内容确保前端能正确解析出JSON数据") f"原始数据的键值为text输出是1个完整的JSON数据不要有多余的前置和后置内容确保前端能正确解析出JSON数据")
prompt = (
f"作为图片识别和理解助手,您的任务是:"
f"\n1. 精确识别图片中的文字内容"
f"\n2. 理解文字语义"
f"\n3. 从以下候选标题中选择最佳匹配项:"
f"\n [{antiques_selected}]"
f"\n\n### 输出要求:"
f"\n- 以严格JSON格式输出包含3个字段"
f"\n • `antique`: 匹配的标题(多个用英文分号';'分割最多匹配3个无匹配则空字符串"
f"\n • `text`: 识别出的完整文字"
f"\n • `match_score`: 整体匹配度(0-1的浮点数)1=完全匹配"
f"\n\n### 匹配规则:"
f"\n1. 语义匹配优先于字面匹配"
f"\n2. 考虑同义词、近义词和描述性匹配"
f"\n3. 允许部分匹配(如'青铜酒器'匹配'青铜器'"
f"\n4. 若无明确匹配项,`antique`返回空字符串"
f"\n\n### 重要:"
f"\n- 输出必须是可直接解析的JSON无任何前置/后置文本"
f"\n- 匹配度评分需客观反映文本与候选标题的相似度"
)
file = request.files['file'] file = request.files['file']
if file.filename == '': if file.filename == '':
@@ -221,12 +241,23 @@ def upload_file(tenant_id,mesum_id):
message = response.choices[0].message message = response.choices[0].message
parsed_json_res = parse_markdown_json(message.content) parsed_json_res = parse_markdown_json(message.content)
parsed_json_data = {"antique": "", "text": "", "match_score": 0} parsed_json_data = {"antique": "", "text": "", "match_score": 0}
matchedArray = []
if parsed_json_res.get('success') is True: if parsed_json_res.get('success') is True:
parsed_json_data = parsed_json_res.get('data') parsed_json_data = parsed_json_res.get('data')
for item in labels_with_id: matchedAntiqueArray = parsed_json_data.get('antique').split(';') # 识别出的文物的数组,中间以';'分割,可能有多个
if item['label'] == parsed_json_data.get('antique'): if len(matchedAntiqueArray) ==1: # 只有一个匹配项,直接返回
parsed_json_data['id'] = item.get('id') for item in labels_with_id:
if item['label'] == parsed_json_data.get('antique'):
parsed_json_data['id'] = item.get('id')
else: # 有多个匹配项,需要进行多个匹配
for label in matchedAntiqueArray:
antique= {'label':label}
for item in labels_with_id:
if item['label'] == label:
antique['id'] = item.get('id')
matchedArray.append(antique)
if len(matchedArray) > 0:
parsed_json_data['matchedArray'] = matchedArray
logging.info(f"{parsed_json_data}") logging.info(f"{parsed_json_data}")
return jsonify({'message': 'File uploaded successfully','text': message.content, return jsonify({'message': 'File uploaded successfully','text': message.content,
'data': parsed_json_data}), 200 'data': parsed_json_data}), 200
@@ -372,6 +403,9 @@ def start_background_cleaner():
# 应用启动时启动清理线程 # 应用启动时启动清理线程
start_background_cleaner() start_background_cleaner()
# 在返回大模型对话的文本中同时生成tts音频由dialog_service 中的StreamSessionManager进行管理
# session_id 为 def create_session(self, tts_model,sample_rate =8000, stream_format='mp3'):
# session_id = str(uuid.uuid4())
@manager.route('/tts_stream/<session_id>',methods=['GET']) @manager.route('/tts_stream/<session_id>',methods=['GET'])
def tts_stream(session_id): def tts_stream(session_id):
session = stream_manager.sessions.get(session_id) session = stream_manager.sessions.get(session_id)
@@ -415,7 +449,6 @@ def tts_stream(session_id):
if session: if session:
# 延迟关闭会话,确保所有数据已发送 # 延迟关闭会话,确保所有数据已发送
stream_manager.close_session(session_id) stream_manager.close_session(session_id)
logging.info(f"Session {session_id} closed. {total_audio_strean_length}")
# 关键响应头设置 # 关键响应头设置
if session['stream_format'] == "wav": if session['stream_format'] == "wav":
@@ -656,7 +689,8 @@ def dialog_tts_post(tenant_id, chat_id):
f"{tts_sample_rate} {tts_stream_format}") f"{tts_sample_rate} {tts_stream_format}")
# 返回音频流URL # 返回音频流URL
return jsonify({"tts_url": audio_stream_url, "audio_stream_id": audio_stream_id, return jsonify({"tts_url": audio_stream_url, "audio_stream_id": audio_stream_id,
"sample_rate":tts_sample_rate, "stream_format":tts_stream_format,}) "sample_rate":tts_sample_rate, "stream_format":tts_stream_format,
"ws_url":audio_stream_url})
except Exception as e: except Exception as e:
logging.error(f"请求处理失败: {str(e)}", exc_info=True) logging.error(f"请求处理失败: {str(e)}", exc_info=True)
@@ -802,6 +836,15 @@ def minio_put_obj(tenant_id):
except Exception as e: except Exception as e:
return get_error_data_result(message=f"minio put object error {e}") return get_error_data_result(message=f"minio put object error {e}")
@manager.route('/minio/list/<bucket>/<prefix>', methods=['GET'])
@token_required
def list_objects(tenant_id,bucket: str, prefix: str = "", recursive: bool = True):
try:
result=minio_client.list_objects(bucket,prefix ,True)
return get_result(data=result)
except Exception as e:
return get_error_data_result(message=f"minio put list objects error {e}")
#------------------------------------------------ #------------------------------------------------
def audio_fade_in(audio_data, fade_length): def audio_fade_in(audio_data, fade_length):
# 假设音频数据是16位单声道PCM # 假设音频数据是16位单声道PCM

View File

@@ -1025,6 +1025,11 @@ class MesumOverview(DataBaseModel):
null=True, null=True,
help_text="图片地址", help_text="图片地址",
index=False) index=False)
photo_prefix = CharField(
max_length=255,
null=True,
help_text="文物图片前缀",
index=False)
address = CharField( address = CharField(
max_length=1024, max_length=1024,
null=True, null=True,
@@ -1055,6 +1060,8 @@ class MesumAntique(DataBaseModel):
combined = TextField(null=True) combined = TextField(null=True)
ttsUrl_adult = CharField(max_length=256, null=True) ttsUrl_adult = CharField(max_length=256, null=True)
ttsUrl_child = CharField(max_length=256, null=True) ttsUrl_child = CharField(max_length=256, null=True)
photo_url = CharField(max_length=256, null=True)
orgin_text = TextField(null=True)
class Meta: class Meta:
db_table = 'mesum_antique' db_table = 'mesum_antique'

View File

@@ -0,0 +1,367 @@
import asyncio,logging
from collections import deque
import threading, time,queue,uuid,time,array
from concurrent.futures import ThreadPoolExecutor
ALI_KEY = "sk-a47a3fb5f4a94f66bbaf713779101c75"
from dashscope.api_entities.dashscope_response import SpeechSynthesisResponse
from dashscope.audio.tts import (
ResultCallback as TTSResultCallback,
SpeechSynthesizer as TTSSpeechSynthesizer,
SpeechSynthesisResult as TTSSpeechSynthesisResult,
)
# cyx 2025 01 19 测试cosyvoice 使用tts_v2 版本
from dashscope.audio.tts_v2 import (
ResultCallback as CosyResultCallback,
SpeechSynthesizer as CosySpeechSynthesizer,
AudioFormat,
)
class QwenTTS:
def __init__(self, key,format="mp3",sample_rate=44100, model_name="cosyvoice-v1/longyuan"):
import dashscope
import ssl
logging.info(f"---QwenTTS Construtor-- {format} {sample_rate} {model_name}") # cyx
self.model_name = model_name
dashscope.api_key = key
ssl._create_default_https_context = ssl._create_unverified_context # 禁用验证
self.synthesizer = None
self.callback = None
self.is_cosyvoice = False
self.voice = ""
self.format = format
self.sample_rate = sample_rate
if '/' in model_name:
parts = model_name.split('/', 1)
# 返回分离后的两个字符串parts[0], parts[1]
if parts[0] == 'cosyvoice-v1':
self.is_cosyvoice = True
self.voice = parts[1]
class Callback(TTSResultCallback):
def __init__(self) -> None:
self.dque = deque()
def _run(self):
while True:
if not self.dque:
time.sleep(0)
continue
val = self.dque.popleft()
if val:
yield val
else:
break
def on_open(self):
pass
def on_complete(self):
self.dque.append(None)
def on_error(self, response: SpeechSynthesisResponse):
print("Qwen tts error", str(response))
raise RuntimeError(str(response))
def on_close(self):
pass
def on_event(self, result: TTSSpeechSynthesisResult):
if result.get_audio_frame() is not None:
self.dque.append(result.get_audio_frame())
# --------------------------
class Callback_Cosy(CosyResultCallback):
def __init__(self,on_audio_data) -> None:
self.dque = deque()
self.on_audio_data = on_audio_data
def _run(self):
while True:
if not self.dque:
time.sleep(0)
continue
val = self.dque.popleft()
if val:
yield val
else:
break
def on_open(self):
logging.info("---Qwen tts on_open---")
pass
def on_complete(self):
self.dque.append(None)
def on_error(self, response: SpeechSynthesisResponse):
print("Qwen tts error", str(response))
raise RuntimeError(str(response))
def on_close(self):
# print("---Qwen call back close") # cyx
logging.info("---Qwen tts on_close---")
pass
""" canceled for test 语音大模型CosyVoice
def on_event(self, result: SpeechSynthesisResult):
if result.get_audio_frame() is not None:
self.dque.append(result.get_audio_frame())
"""
def on_event(self, message):
# print(f"recv speech synthsis message {message}")
pass
# 以下适合语音大模型CosyVoice
def on_data(self, data: bytes) -> None:
if len(data) > 0:
if self.on_audio_data:
self.on_audio_data(data)
else:
self.dque.append(data)
# --------------------------
def tts(self, text):
print(f"--QwenTTS--tts_stream begin-- {text} {self.is_cosyvoice} {self.voice}") # cyx
# text = self.normalize_text(text)
try:
# if self.model_name != 'cosyvoice-v1':
if self.is_cosyvoice is False:
self.callback = self.Callback()
TTSSpeechSynthesizer.call(model=self.model_name,
text=text,
callback=self.callback,
format="wav") # format="mp3")
else:
self.callback = self.Callback_Cosy(None)
format =self.get_audio_format(self.format,self.sample_rate)
self.synthesizer = CosySpeechSynthesizer(
model='cosyvoice-v1',
# voice="longyuan", #"longfei",
voice=self.voice,
callback=self.callback,
format=format
)
self.synthesizer.call(text)
except Exception as e:
print(f"---dale---20 error {e}") # cyx
# -----------------------------------
try:
for data in self.callback._run():
#logging.info(f"dashcope return data {len(data)}")
yield data
# print(f"---Qwen return data {num_tokens_from_string(text)}")
# yield num_tokens_from_string(text)
except Exception as e:
raise RuntimeError(f"**ERROR**: {e}")
def init_streaming_call(self, on_data):
try:
self.callback = self.Callback_Cosy(on_data)
format =self.get_audio_format(self.format,self.sample_rate)
self.synthesizer = CosySpeechSynthesizer(
model='cosyvoice-v1',
# voice="longyuan", #"longfei",
voice=self.voice,
callback=self.callback,
format=format
)
except Exception as e:
print(f"---dale---30 error {e}") # cyx
# -----------------------------------
def streaming_call(self,text):
if self.synthesizer:
self.synthesizer.streaming_call(text)
def end_streaming_call(self):
if self.synthesizer:
self.synthesizer.streaming_complete()
def get_audio_format(self, format: str, sample_rate: int):
"""动态获取音频格式"""
from dashscope.audio.tts_v2 import AudioFormat
format_map = {
(8000, 'mp3'): AudioFormat.MP3_8000HZ_MONO_128KBPS,
(8000, 'pcm'): AudioFormat.PCM_8000HZ_MONO_16BIT,
(8000, 'wav'): AudioFormat.WAV_8000HZ_MONO_16BIT,
(16000, 'pcm'): AudioFormat.PCM_16000HZ_MONO_16BIT,
(22050, 'mp3'): AudioFormat.MP3_22050HZ_MONO_256KBPS,
(22050, 'pcm'): AudioFormat.PCM_22050HZ_MONO_16BIT,
(22050, 'wav'): AudioFormat.WAV_22050HZ_MONO_16BIT,
(44100, 'mp3'): AudioFormat.MP3_44100HZ_MONO_256KBPS,
(44100, 'pcm'): AudioFormat.PCM_44100HZ_MONO_16BIT,
(44100, 'wav'): AudioFormat.WAV_44100HZ_MONO_16BIT,
(48000, 'mp3'): AudioFormat.MP3_48000HZ_MONO_256KBPS,
(48000, 'pcm'): AudioFormat.PCM_48000HZ_MONO_16BIT,
(48000, 'wav'):AudioFormat.WAV_48000HZ_MONO_16BIT
}
return format_map.get((sample_rate, format), AudioFormat.MP3_16000HZ_MONO_128KBPS)
class StreamSessionManager:
def __init__(self):
self.sessions = {} # {session_id: {'tts_model': obj, 'buffer': queue, 'task_queue': Queue}}
self.lock = threading.Lock()
self.executor = ThreadPoolExecutor(max_workers=30) # 固定大小线程池
self.gc_interval = 300 # 5分钟清理一次 5 x 60 300秒
self.gc_tts = 10 # 10s 大模型开始输出文本有可能需要比较久2025年5 24 从3s->10s
def create_session(self, tts_model,sample_rate =8000, stream_format='mp3',voice='cosyvoice-v1/longxiaochun'):
session_id = str(uuid.uuid4())
def on_audio_data(chunk):
session = self.sessions.get(session_id)
first_chunk = not session['tts_chunk_data_valid']
if session['stream_format'] == 'wav':
if first_chunk:
chunk_len = len(chunk)
if chunk_len > 2048:
session['buffer'].put(audio_fade_in(chunk, 1024))
else:
session['buffer'].put(audio_fade_in(chunk, chunk_len))
else:
session['buffer'].put(chunk)
else:
session['buffer'].put(chunk)
session['last_active'] = time.time()
session['audio_chunk_count'] = session['audio_chunk_count'] + 1
if session['tts_chunk_data_valid'] is False:
session['tts_chunk_data_valid'] = True # 20250510 增加表示连接TTS后台已经返回可以通知前端了
with self.lock:
ali_tts_model = QwenTTS(ALI_KEY,stream_format, sample_rate,voice.split('@')[0])
self.sessions[session_id] = {
'tts_model': ali_tts_model, #tts_model,
'buffer': queue.Queue(maxsize=300), # 线程安全队列
'task_queue': queue.Queue(),
'active': True,
'last_active': time.time(),
'audio_chunk_count':0,
'finished': threading.Event(), # 添加事件对象
'sample_rate':sample_rate,
'stream_format':stream_format,
"tts_chunk_data_valid":False,
'voice':voice,
}
self.sessions[session_id]['tts_model'].init_streaming_call(on_audio_data)
# 启动任务处理线程
threading.Thread(target=self._process_tasks, args=(session_id,), daemon=True).start()
return session_id
def append_text(self, session_id, text):
with self.lock:
session = self.sessions.get(session_id)
if not session: return
# 将文本放入任务队列(非阻塞)
#logging.info(f"StreamSessionManager append_text {text}")
try:
session['task_queue'].put(text, block=False)
except queue.Full:
logging.warning(f"Session {session_id} task queue full")
def _process_tasks(self, session_id):
"""任务处理线程(每个会话独立)"""
while True:
session = self.sessions.get(session_id)
if not session or not session['active']:
break
try:
#logging.info(f"StreamSessionManager _process_tasks {session['task_queue'].qsize()}")
# 合并多个文本块最多等待50ms
texts = []
while len(texts) < 5: # 最大合并5个文本块
try:
text = session['task_queue'].get(timeout=0.1)
#logging.info(f"StreamSessionManager _process_tasks --0 {len(texts)}")
texts.append(text)
except queue.Empty:
break
if texts:
session['last_active'] = time.time() # 如果有处理文本,重置活跃时间
# 提交到线程池处理
#future=self.executor.submit(
# self._generate_audio,
# session_id,
# ' '.join(texts) # 合并文本减少请求次数
#)
#future.result() # 等待转换任务执行完毕
session['tts_model'].streaming_call(''.join(texts))
session['last_active'] = time.time()
# 会话超时检查
if time.time() - session['last_active'] > self.gc_interval:
self.close_session(session_id)
break
if time.time() - session['last_active'] > self.gc_tts:
session['tts_model'].end_streaming_call()
session['finished'].set()
break
except Exception as e:
logging.error(f"Task processing error: {str(e)}")
def _generate_audio(self, session_id, text):
"""实际生成音频(线程池执行)"""
session = self.sessions.get(session_id)
if not session: return
# logging.info(f"_generate_audio:{text}")
first_chunk = True
logging.info(f"转换开始!!! {text}")
try:
for chunk in session['tts_model'].tts(text,session['sample_rate'],session['stream_format']):
if session['stream_format'] == 'wav':
if first_chunk:
chunk_len = len(chunk)
if chunk_len > 2048:
session['buffer'].put(audio_fade_in(chunk,1024))
else:
session['buffer'].put(audio_fade_in(chunk, chunk_len))
first_chunk = False
else:
session['buffer'].put(chunk)
else:
session['buffer'].put(chunk)
session['last_active'] = time.time()
session['audio_chunk_count'] = session['audio_chunk_count'] + 1
if session['tts_chunk_data_valid'] is False:
session['tts_chunk_data_valid'] = True #20250510 增加表示连接TTS后台已经返回可以通知前端了
logging.info(f"转换结束!!! {session['audio_chunk_count'] }")
except Exception as e:
session['buffer'].put(f"ERROR:{str(e)}")
logging.info(f"--_generate_audio--error {str(e)}")
def close_session(self, session_id):
with self.lock:
if session_id in self.sessions:
logging.info(f"--Session {session_id} close_session")
# 标记会话为不活跃
self.sessions[session_id]['active'] = False
# 延迟2秒后清理资源
threading.Timer(1, self._clean_session, args=[session_id]).start()
def _clean_session(self, session_id):
with self.lock:
if session_id in self.sessions:
del self.sessions[session_id]
def get_session(self, session_id):
return self.sessions.get(session_id)
stream_manager_w_stream = StreamSessionManager()
def audio_fade_in(audio_data, fade_length):
# 假设音频数据是16位单声道PCM
# 将二进制数据转换为整数数组
samples = array.array('h', audio_data)
# 对前fade_length个样本进行淡入处理
for i in range(fade_length):
fade_factor = i / fade_length
samples[i] = int(samples[i] * fade_factor)
# 将整数数组转换回二进制数据
return samples.tobytes()

View File

@@ -46,7 +46,8 @@ class MesumAntiqueService(CommonService):
if mesum_brief: if mesum_brief:
categories_text= mesum_brief[0].category categories_text= mesum_brief[0].category
# 统一替换中文分号为英文分号,并去除末尾分号 # 统一替换中文分号为英文分号,并去除末尾分号
categories_text = categories_text.replace("", ";").rstrip(";") if categories_text:
categories_text = categories_text.replace("", ";").rstrip(";")
# 分割并清理空格/空值 # 分割并清理空格/空值
mesum_antique_categories = [dynasty.strip() for dynasty in categories_text.split(";") if dynasty.strip()] mesum_antique_categories = [dynasty.strip() for dynasty in categories_text.split(";") if dynasty.strip()]

View File

@@ -35,6 +35,7 @@ from api.utils.file_utils import get_project_base_directory
from peewee import fn from peewee import fn
import threading, queue,uuid,time,array import threading, queue,uuid,time,array
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from api.db.services.ali_tts_service import (stream_manager_w_stream as stream_manager)
def audio_fade_in(audio_data, fade_length): def audio_fade_in(audio_data, fade_length):
# 假设音频数据是16位单声道PCM # 假设音频数据是16位单声道PCM
@@ -173,8 +174,7 @@ class StreamSessionManager:
def get_session(self, session_id): def get_session(self, session_id):
return self.sessions.get(session_id) return self.sessions.get(session_id)
stream_manager_bk = StreamSessionManager()
stream_manager = StreamSessionManager()
class DialogService(CommonService): class DialogService(CommonService):
model = Dialog model = Dialog
@@ -756,16 +756,20 @@ def chat(dialog, messages, stream=True, **kwargs):
if stream: if stream:
last_ans = "" last_ans = ""
answer = "" answer = ""
audio_url = None
if not kwargs.get('tts_disable'):
# 创建TTS会话提前初始化 # 创建TTS会话提前初始化
tts_session_id = stream_manager.create_session(tts_mdl,sample_rate=tts_sample_rate,stream_format=tts_stream_format) tts_session_id = stream_manager.create_session(tts_mdl,sample_rate=tts_sample_rate,stream_format=tts_stream_format,
tts_session = stream_manager.get_session(tts_session_id) voice = kwargs.get('tts_model'))
audio_url = f"/tts_stream/{tts_session_id}" tts_session = stream_manager.get_session(tts_session_id)
audio_url = f"/tts_stream/{tts_session_id}"
send_tts_url = False send_tts_url = False
chunk_buffer = [] # 新增文本缓冲 chunk_buffer = [] # 新增文本缓冲
last_flush_time = time.time() # 初始化时间戳 last_flush_time = time.time() # 初始化时间戳
# 下面优先处理知识库中没有找到相关内容 cyx 20250323 修改 # 下面优先处理知识库中没有找到相关内容 cyx 20250323 修改
if not kwargs["knowledge"] or kwargs["knowledge"] =="" or len(kwargs["knowledge"]) < 4: if not kwargs["knowledge"] or kwargs["knowledge"] =="" or len(kwargs["knowledge"]) < 4:
stream_manager.append_text(tts_session_id, "未找到相关内容") if not kwargs.get('tts_disable'):
stream_manager.append_text(tts_session_id, "未找到相关内容")
yield { yield {
"answer": "未找到相关内容", "answer": "未找到相关内容",
"delta_ans": "未找到相关内容", "delta_ans": "未找到相关内容",
@@ -810,18 +814,19 @@ def chat(dialog, messages, stream=True, **kwargs):
yield {"answer": answer, "delta_ans": sanitized_text, "reference": {}} yield {"answer": answer, "delta_ans": sanitized_text, "reference": {}}
""" """
# 首块返回音频URL # 首块返回音频URL
if send_tts_url is False and tts_session['tts_chunk_data_valid'] is True: if send_tts_url is False and not kwargs.get('tts_disable'):
yield { if tts_session['tts_chunk_data_valid'] is True:
"answer": answer, yield {
"delta_ans": sanitized_text, "answer": answer,
"session_id": tts_session_id, "delta_ans": sanitized_text,
"reference": {}, "session_id": tts_session_id,
"audio_stream_url": audio_url, "reference": {},
"sample_rate":tts_sample_rate, "audio_stream_url": audio_url,
"stream_format":tts_stream_format, "sample_rate":tts_sample_rate,
} "stream_format":tts_stream_format,
send_tts_url = True # 发送一次tts url 给前端即可,不能重复发送 }
logging.info(f"--chat retur tts url {audio_url}") send_tts_url = True # 发送一次tts url 给前端即可,不能重复发送
logging.info(f"--chat retur tts url {audio_url}")
else: else:
yield {"answer": answer, "delta_ans": sanitized_text,"reference": {}} yield {"answer": answer, "delta_ans": sanitized_text,"reference": {}}

View File

@@ -1 +1,4 @@
TIMEZONE=Asia/Shanghai TIMEZONE=Asia/Shanghai
DASHSCOPE_API_KEY = sk-a47a3fb5f4a94f66bbaf713779101c75

467
asr-monitor-test/app.log Normal file
View File

@@ -0,0 +1,467 @@
INFO: Started server process [50175]
INFO: Waiting for application startup.
21:51:05.972 - INFO - 监控服务已启动
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:9580 (Press CTRL+C to quit)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ASR & Monitor Service Start
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
INFO: 35.203.210.77:60796 - "GET / HTTP/1.1" 404 Not Found
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
INFO: 221.238.94.141:45448 - "GET / HTTP/1.0" 404 Not Found
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
INFO: 221.238.94.141:45526 - "OPTIONS / HTTP/1.0" 404 Not Found
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
INFO: 221.238.94.141:45640 - "GET /nice%20ports%2C/Trinity.txt.bak HTTP/1.0" 404 Not Found
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
INFO: 221.238.94.141:38084 - "GET /api HTTP/1.0" 404 Not Found
WARNING: Invalid HTTP request received.
INFO: 221.238.94.141:38098 - "GET /hazelcast/rest/cluster HTTP/1.0" 404 Not Found
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.
INFO: 221.238.94.141:38374 - "GET / HTTP/1.1" 404 Not Found
06:57:54.681 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 106.55.206.109:0 - "GET /auth/verify HTTP/1.1" 200 OK
06:57:55.071 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.144.107.210:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
06:59:18.037 - INFO - Creating TTS request: {'text': '陶鬲形猪首盖盉通高27厘米腹径20.5厘米2010年安钢大道M78出土中国社会科学院考古研究所藏。器型呈猪首状长嘴圆眼微凸双耳直立憨态可掬。肩部饰三角绳纹腹及袋足饰细绳纹作为温酒的容器加热后美酒从它的口中流出。', 'session_id': '0b4cdbaeaf9111efa53df171065841e8', 'delay_gen_audio': True, 'tts_sample_rate': 48000, 'tts_stream_format': 'mp3', 'model_name': 'cosyvoice-v1/longyuan@Tongyi-Qianwen', 'sample_rate': 48000, 'stream_format': 'mp3'}
INFO: 43.144.107.28:0 - "POST /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts?device_id=17513727656058688719 HTTP/1.1" 200 OK
INFO: ('1.13.185.116', 50174) - "WebSocket /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts/d45fdb6d-9bcb-4458-9b83-d63f3eb69720" [accepted]
06:59:19.472 - INFO - 新连接建立: 414fa19a-4425-4b60-b172-ec661a3457b2
06:59:19.472 - INFO - 已经启动 start tts task {audio_stream_id}
06:59:19.473 - INFO - ---begin--init QwenTTS-- mp3 48000 cosyvoice-v1/longyuan@Tongyi-Qianwen cosyvoice-v1/longyuan
06:59:19.473 - INFO - QwenTTS--get_audio_format-- mp3 48000
06:59:19.473 - INFO - setup_tts longyuan MP3 with 48000Hz sample rate, mono channel, 256kbps
INFO: connection open
06:59:19.617 - INFO - Websocket connected
06:59:19.729 - INFO - Qwen CosyVoice tts open
06:59:28.170 - INFO - --data_handler on_complete
06:59:28.170 - INFO - Qwen CosyVoice tts close
06:59:28.170 - INFO - --tts task event set error = None
INFO: connection closed
07:00:54.600 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 43.144.107.210:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:00:54.946 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.140.60.44:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:04:53.633 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 43.144.107.28:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:04:53.793 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.140.60.44:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:04:57.090 - INFO - Creating TTS request: {'text': '1945年10月通县民主政府成立机关设在潮白河西岸侯各庄西北角的一座菩萨庙圆通庵领导全县人民进行反蒋解放斗争。', 'session_id': '0b4cdbaeaf9111efa53df171065841e8', 'delay_gen_audio': True, 'tts_sample_rate': 48000, 'tts_stream_format': 'mp3', 'model_name': 'cosyvoice-v1/longyuan@Tongyi-Qianwen', 'sample_rate': 48000, 'stream_format': 'mp3'}
INFO: 43.140.60.33:0 - "POST /tts/chats/9cb04ecead5111ef99e80242ac120006/tts?device_id=17513727656058688719 HTTP/1.1" 200 OK
INFO: ('1.13.185.116', 33684) - "WebSocket /tts/chats/9cb04ecead5111ef99e80242ac120006/tts/c83abcdf-225e-41a1-a20d-0cc228b7c547" [accepted]
07:04:57.520 - INFO - 新连接建立: 2025badf-3414-48c6-8382-76c51673d9ef
07:04:57.520 - INFO - 已经启动 start tts task {audio_stream_id}
07:04:57.520 - INFO - ---begin--init QwenTTS-- mp3 48000 cosyvoice-v1/longyuan@Tongyi-Qianwen cosyvoice-v1/longyuan
07:04:57.520 - INFO - QwenTTS--get_audio_format-- mp3 48000
07:04:57.520 - INFO - setup_tts longyuan MP3 with 48000Hz sample rate, mono channel, 256kbps
INFO: connection open
07:04:57.639 - INFO - Websocket connected
07:04:57.768 - INFO - Qwen CosyVoice tts open
07:05:01.717 - INFO - --data_handler on_complete
07:05:01.717 - INFO - Qwen CosyVoice tts close
07:05:01.717 - INFO - --tts task event set error = None
INFO: connection closed
07:06:17.189 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 43.140.60.44:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:06:17.383 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.140.60.33:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:06:20.644 - INFO - Creating TTS request: {'text': '元大都水利建设最终形成了金水河、高梁河两进,坝河、通惠河两出,瓮山泊、积水潭两大蓄水水面格局,水利景观呈现“一心、一廊”的发展特征。城内供水区以积水潭为核心,通惠河漕运走廊连接北运河与白浮泉,串联起自然山水、寺庙、集市、私园、公共桥闸等景观,充分体现了城市、水利、景观三者之间的相互作用。元代水利建设对北京城市水利景观和生态环境具有深远的影响。', 'session_id': '0b4cdbaeaf9111efa53df171065841e8', 'delay_gen_audio': True, 'tts_sample_rate': 48000, 'tts_stream_format': 'mp3', 'model_name': 'cosyvoice-v1/longyuan@Tongyi-Qianwen', 'sample_rate': 48000, 'stream_format': 'mp3'}
INFO: 43.140.60.33:0 - "POST /tts/chats/9cb04ecead5111ef99e80242ac120006/tts?device_id=17513727656058688719 HTTP/1.1" 200 OK
INFO: ('1.13.185.116', 47246) - "WebSocket /tts/chats/9cb04ecead5111ef99e80242ac120006/tts/7d2bd2dd-dfc0-43e6-9b23-08e5251bb954" [accepted]
07:06:21.077 - INFO - 新连接建立: caa0458b-7662-4ebe-9a6b-480c57c400b6
07:06:21.077 - INFO - 已经启动 start tts task {audio_stream_id}
07:06:21.077 - INFO - ---begin--init QwenTTS-- mp3 48000 cosyvoice-v1/longyuan@Tongyi-Qianwen cosyvoice-v1/longyuan
07:06:21.078 - INFO - QwenTTS--get_audio_format-- mp3 48000
07:06:21.078 - INFO - setup_tts longyuan MP3 with 48000Hz sample rate, mono channel, 256kbps
INFO: connection open
07:06:21.194 - INFO - Websocket connected
07:06:21.324 - INFO - Qwen CosyVoice tts open
07:06:32.422 - INFO - --data_handler on_complete
07:06:32.422 - INFO - Qwen CosyVoice tts close
07:06:32.422 - INFO - --tts task event set error = None
INFO: connection closed
07:44:06.202 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:44:06.290 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:44:52.287 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:44:52.440 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:45:23.056 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:45:23.159 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:54:21.106 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:54:21.161 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:54:28.720 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:54:28.799 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:54:35.913 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:54:36.001 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:54:36.411 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:54:36.509 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:55:22.522 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:55:22.582 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
07:56:58.308 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
07:56:58.385 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:07:38.414 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:07:38.499 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:10:57.429 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:10:57.486 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:11:53.340 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:11:53.398 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:16:33.756 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:16:33.813 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:17:30.384 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:17:30.490 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:18:33.644 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:18:33.709 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:19:50.951 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:19:51.034 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:21:37.950 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:21:38.023 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:22:05.386 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:22:05.441 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:23:13.075 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:23:13.153 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:24:49.462 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:24:49.563 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:26:08.181 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:26:08.282 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:26:49.301 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:26:49.374 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:27:41.833 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:27:41.893 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:28:50.023 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:28:50.083 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:29:21.179 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:29:21.387 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:30:19.839 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:30:20.031 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:31:11.560 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:31:11.723 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:33:16.896 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:33:17.108 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:33:51.413 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:33:51.479 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:35:10.447 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:35:10.505 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:36:24.091 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:36:24.187 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:37:18.392 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:37:18.507 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:38:42.906 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 106.55.206.109:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:38:43.148 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.140.60.33:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:49:31.793 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 106.55.206.109:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:49:31.991 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.140.60.33:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:51:30.837 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:51:30.938 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:55:28.363 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:55:28.421 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
08:56:53.353 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
08:56:53.419 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:00:01.531 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:00:01.590 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:00:07.397 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:00:07.486 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:00:47.232 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:00:47.306 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:01:19.694 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:01:19.959 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:02:46.650 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:02:46.718 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:03:29.115 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:03:29.384 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:05:12.089 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:05:12.145 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:10:24.333 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:10:24.586 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:11:14.693 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:11:14.781 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:11:45.666 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:11:45.755 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
09:12:29.573 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
09:12:29.813 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
10:36:24.208 - INFO - verify_token user={'user_id': '689cac0d-062e-45df-88bf-d7c4b16ff226', 'openid': 'obKSz7bHxzDFWnLHmOrnY_-8D6fI', 'phone': '13810887276', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2ODljYWMwZC0wNjJlLTQ1ZGYtODhiZi1kN2M0YjE2ZmYyMjYiLCJleHAiOjE3NTIwMzM3NzV9.Go3vld8ijajTsYSwzA45YS47O9thXdnLtrF0G7x1BAM', 'balance': 0, 'status': 1, 'last_login_time': 1751428975, 'create_time': 1748951630, 'create_date': datetime.datetime(2025, 6, 3, 19, 53, 50), 'update_time': 1751428975, 'update_date': datetime.datetime(2025, 7, 2, 12, 2, 55)}
INFO: 223.104.41.17:0 - "GET /auth/verify HTTP/1.1" 200 OK
10:36:24.285 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 223.104.41.17:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
10:36:34.664 - INFO - verify_token user={'user_id': '689cac0d-062e-45df-88bf-d7c4b16ff226', 'openid': 'obKSz7bHxzDFWnLHmOrnY_-8D6fI', 'phone': '13810887276', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2ODljYWMwZC0wNjJlLTQ1ZGYtODhiZi1kN2M0YjE2ZmYyMjYiLCJleHAiOjE3NTIwMzM3NzV9.Go3vld8ijajTsYSwzA45YS47O9thXdnLtrF0G7x1BAM', 'balance': 0, 'status': 1, 'last_login_time': 1751428975, 'create_time': 1748951630, 'create_date': datetime.datetime(2025, 6, 3, 19, 53, 50), 'update_time': 1751428975, 'update_date': datetime.datetime(2025, 7, 2, 12, 2, 55)}
INFO: 223.104.41.17:0 - "GET /auth/verify HTTP/1.1" 200 OK
10:36:34.746 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 223.104.41.17:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
10:36:38.066 - INFO - Creating TTS request: {'text': '铜条形驭马器长18.3厘米宽1.4厘米高2.2厘米2021年邵家棚M109出土安阳市文物考古研究院藏。\n', 'session_id': '0b4cdbaeaf9111efa53df171065841e8', 'delay_gen_audio': True, 'tts_sample_rate': 48000, 'tts_stream_format': 'mp3', 'model_name': 'cosyvoice-v1/longyuan@Tongyi-Qianwen', 'sample_rate': 48000, 'stream_format': 'mp3'}
INFO: 223.104.41.17:0 - "POST /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts?device_id=17507669394291632844 HTTP/1.1" 200 OK
INFO: ('1.13.185.116', 36130) - "WebSocket /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts/de035ae4-81d8-408b-b551-2eac17b07e50" [accepted]
10:36:38.312 - INFO - 新连接建立: 733148f7-d2ac-4fa2-ad3a-b41fb684d08e
10:36:38.312 - INFO - 已经启动 start tts task {audio_stream_id}
INFO: connection open
10:36:38.313 - INFO - ---begin--init QwenTTS-- mp3 48000 cosyvoice-v1/longyuan@Tongyi-Qianwen cosyvoice-v1/longyuan
10:36:38.313 - INFO - QwenTTS--get_audio_format-- mp3 48000
10:36:38.313 - INFO - setup_tts longyuan MP3 with 48000Hz sample rate, mono channel, 256kbps
10:36:38.425 - INFO - Websocket connected
10:36:38.560 - INFO - Qwen CosyVoice tts open
10:36:42.366 - INFO - --data_handler on_complete
10:36:42.366 - INFO - Qwen CosyVoice tts close
10:36:42.366 - INFO - --tts task event set error = None
INFO: connection closed
10:36:49.363 - INFO - verify_token user={'user_id': '689cac0d-062e-45df-88bf-d7c4b16ff226', 'openid': 'obKSz7bHxzDFWnLHmOrnY_-8D6fI', 'phone': '13810887276', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2ODljYWMwZC0wNjJlLTQ1ZGYtODhiZi1kN2M0YjE2ZmYyMjYiLCJleHAiOjE3NTIwMzM3NzV9.Go3vld8ijajTsYSwzA45YS47O9thXdnLtrF0G7x1BAM', 'balance': 0, 'status': 1, 'last_login_time': 1751428975, 'create_time': 1748951630, 'create_date': datetime.datetime(2025, 6, 3, 19, 53, 50), 'update_time': 1751428975, 'update_date': datetime.datetime(2025, 7, 2, 12, 2, 55)}
INFO: 223.104.41.17:0 - "GET /auth/verify HTTP/1.1" 200 OK
10:36:49.440 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 223.104.41.17:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
10:36:53.851 - INFO - Creating TTS request: {'text': '铜铙①高18厘米口长13.2厘米口宽10.1厘米②高13厘米口长11.6厘米口宽8.7厘米③高11.9厘米口长9.4厘米口宽7厘米1983年戚家庄东地出土安阳市文物考古研究院藏。\n', 'session_id': '0b4cdbaeaf9111efa53df171065841e8', 'delay_gen_audio': True, 'tts_sample_rate': 48000, 'tts_stream_format': 'mp3', 'model_name': 'cosyvoice-v1/longyuan@Tongyi-Qianwen', 'sample_rate': 48000, 'stream_format': 'mp3'}
INFO: 223.104.41.17:0 - "POST /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts?device_id=17507669394291632844 HTTP/1.1" 200 OK
INFO: ('1.13.185.116', 49668) - "WebSocket /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts/c6355d58-9d94-4726-bf00-0581bcdb0e28" [accepted]
10:36:54.094 - INFO - 新连接建立: fc397484-625d-4db9-9fba-6d5a8949a57a
10:36:54.094 - INFO - 已经启动 start tts task {audio_stream_id}
10:36:54.094 - INFO - ---begin--init QwenTTS-- mp3 48000 cosyvoice-v1/longyuan@Tongyi-Qianwen cosyvoice-v1/longyuan
10:36:54.094 - INFO - QwenTTS--get_audio_format-- mp3 48000
10:36:54.094 - INFO - setup_tts longyuan MP3 with 48000Hz sample rate, mono channel, 256kbps
INFO: connection open
10:36:54.202 - INFO - Websocket connected
10:36:54.341 - INFO - Qwen CosyVoice tts open
10:37:05.913 - INFO - --data_handler on_complete
10:37:05.914 - INFO - Qwen CosyVoice tts close
10:37:05.914 - INFO - --tts task event set error = None
INFO: connection closed
INFO: ('1.13.185.116', 59460) - "WebSocket /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts/9d5e77cc-ad9b-4a60-812f-880d2f182543" [accepted]
10:37:23.067 - INFO - 新连接建立: d3f6a319-1597-4283-8330-a26c2582c91c
INFO: connection open
10:37:23.121 - INFO - 代理文本流: completions_url=http://localhost:9380/api/v1/chats/39e9a2ba5a4711f0865bbb55c66f9471/completions {'question': '请介绍铜铙', 'stream': True, 'tts_model': 'cosyvoice-v1/longyuan@Tongyi-Qianwen', 'tts_sample_rate': 8000, 'tts_stream_format': 'mp3', 'tts_disable': True}
10:37:23.121 - INFO - ---begin--init QwenTTS-- mp3 8000 cosyvoice-v1/longyuan@Tongyi-Qianwen cosyvoice-v1/longyuan
10:37:23.121 - INFO - QwenTTS--get_audio_format-- mp3 8000
10:37:23.121 - INFO - setup_tts longyuan MP3 with 8000Hz sample rate, mono channel, 128kbps
10:37:26.511 - INFO - HTTP Request: POST http://localhost:9380/api/v1/chats/39e9a2ba5a4711f0865bbb55c66f9471/completions "HTTP/1.1 200 OK"
10:37:26.511 - INFO - 响应状态: HTTP 200
10:37:26.511 - INFO - 开始处理SSE流
10:37:26.651 - INFO - Websocket connected
10:37:26.775 - INFO - Qwen CosyVoice tts open
INFO: ('1.13.185.116', 49122) - "WebSocket /tts/chats/39e9a2ba5a4711f0865bbb55c66f9471/tts/64880966-3b94-4fae-ac7e-e37e16768c2a" [accepted]
10:37:26.927 - INFO - 新连接建立: f666fc5e-48bd-4451-ac13-65c656784d3f
INFO: connection open
10:37:39.969 - INFO - SSE流处理完成事件数: 23
INFO: connection closed
INFO: connection closed
10:37:45.801 - INFO - 发送时检测到断开连接: f666fc5e-48bd-4451-ac13-65c656784d3f,
10:38:00.989 - ERROR - error from callback <bound method SpeechSynthesizer.on_message of <dashscope.audio.tts_v2.speech_synthesizer.SpeechSynthesizer object at 0x7f25f18a86a0>>: {"header":{"task_id":"8ed5859fd51746068babf1776c7c84df","event":"task-failed","error_code":"InvalidParameter","error_message":"request timeout after 23 seconds.","attributes":{}},"payload":{}}
10:38:00.989 - ERROR - error from callback <bound method SpeechSynthesizer.on_error of <dashscope.audio.tts_v2.speech_synthesizer.SpeechSynthesizer object at 0x7f25f18a86a0>>: websocket closed due to websocket closed due to {"header":{"task_id":"8ed5859fd51746068babf1776c7c84df","event":"task-failed","error_code":"InvalidParameter","error_message":"request timeout after 23 seconds.","attributes":{}},"payload":{}}
10:38:00.989 - INFO - tearing down on exception websocket closed due to websocket closed due to websocket closed due to {"header":{"task_id":"8ed5859fd51746068babf1776c7c84df","event":"task-failed","error_code":"InvalidParameter","error_message":"request timeout after 23 seconds.","attributes":{}},"payload":{}}
10:38:00.989 - ERROR - close status: 1007
Qwen tts error {"header":{"task_id":"8ed5859fd51746068babf1776c7c84df","event":"task-failed","error_code":"InvalidParameter","error_message":"request timeout after 23 seconds.","attributes":{}},"payload":{}}
websocket closed due to {"header":{"task_id":"8ed5859fd51746068babf1776c7c84df","event":"task-failed","error_code":"InvalidParameter","error_message":"request timeout after 23 seconds.","attributes":{}},"payload":{}}
websocket closed due to websocket closed due to {"header":{"task_id":"8ed5859fd51746068babf1776c7c84df","event":"task-failed","error_code":"InvalidParameter","error_message":"request timeout after 23 seconds.","attributes":{}},"payload":{}}
websocket closed due to websocket closed due to websocket closed due to {"header":{"task_id":"8ed5859fd51746068babf1776c7c84df","event":"task-failed","error_code":"InvalidParameter","error_message":"request timeout after 23 seconds.","attributes":{}},"payload":{}}
INFO: 183.163.60.214:0 - "GET /auth/get_museum_list HTTP/1.1" 401 Unauthorized
Exception in thread Thread-1544 (_process_tasks):
Traceback (most recent call last):
File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
self.run()
File "/usr/lib/python3.10/threading.py", line 953, in run
self._target(*self._args, **self._kwargs)
File "/home/ubuntu/ragflow/asr-monitor-test/app/tts_service.py", line 192, in _process_tasks
session['tts_model'].end_streaming_call()
File "/home/ubuntu/ragflow/asr-monitor-test/app/tts_service.py", line 1005, in end_streaming_call
self.synthesizer.streaming_complete()
File "/home/ubuntu/ragflow/asr-monitor/venv/lib/python3.10/site-packages/dashscope/audio/tts_v2/speech_synthesizer.py", line 374, in streaming_complete
self.__send_str(request)
File "/home/ubuntu/ragflow/asr-monitor/venv/lib/python3.10/site-packages/dashscope/audio/tts_v2/speech_synthesizer.py", line 285, in __send_str
self.ws.send(data)
File "/home/ubuntu/ragflow/asr-monitor/venv/lib/python3.10/site-packages/websocket/_app.py", line 291, in send
raise WebSocketConnectionClosedException("Connection is already closed.")
websocket._exceptions.WebSocketConnectionClosedException: Connection is already closed.
INFO: 183.163.60.214:0 - "GET /auth/get_museum_list HTTP/1.1" 401 Unauthorized
INFO: 123.115.202.182:0 - "GET /auth/get_museum_list HTTP/1.1" 401 Unauthorized
11:23:13.037 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:23:13.104 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:25:03.125 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:25:03.187 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:26:37.995 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:26:38.077 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:27:45.347 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:27:45.695 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:28:25.996 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 61.141.77.65:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:28:26.062 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 61.141.77.65:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:29:40.340 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:29:40.639 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:30:15.537 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 61.141.77.65:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:30:15.603 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 61.141.77.65:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:30:52.210 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 61.141.77.65:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:30:52.288 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 61.141.77.65:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:35:02.399 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:35:02.737 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:36:02.938 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:36:03.034 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:37:17.719 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:37:17.802 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:37:47.273 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 61.141.77.65:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:37:47.367 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 61.141.77.65:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:39:13.999 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:39:14.337 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:41:51.134 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:41:51.495 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:43:01.977 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 163.125.202.178:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:43:02.050 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 163.125.202.178:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:44:30.162 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 43.144.107.210:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:44:30.345 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.140.60.44:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK
11:45:40.101 - INFO - verify_token user={'user_id': '76538cf0-a6cf-4aa8-8440-382dd2330384', 'openid': 'obKSz7V6a-avAF-vtQrnk_rnuSGE', 'phone': '18676776176', 'email': None, 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjUzOGNmMC1hNmNmLTRhYTgtODQ0MC0zODJkZDIzMzAzODQiLCJleHAiOjE3NTI1ODI1ODN9.GONQPAzcWnYQN2i14zfNhjU8OT1to7fWn6ZA6lNdSHY', 'balance': 0, 'status': 1, 'last_login_time': 1751977783, 'create_time': 1748960538, 'create_date': datetime.datetime(2025, 6, 3, 22, 22, 18), 'update_time': 1751977783, 'update_date': datetime.datetime(2025, 7, 8, 20, 29, 43)}
INFO: 43.144.107.210:0 - "GET /auth/verify HTTP/1.1" 200 OK
11:45:40.260 - INFO - get_museum_id_auth=[1, 2, 3]
INFO: 43.140.60.44:0 - "GET /auth/get_museum_id_auth HTTP/1.1" 200 OK

View File

@@ -0,0 +1,148 @@
import array
import asyncio
import base64
import binascii
import datetime
import gzip
import io
import json
import logging
import os
import queue
import re
import threading
import time
import time
import uuid
from collections import deque
from concurrent.futures import ThreadPoolExecutor
from copy import deepcopy
from datetime import timedelta
from io import BytesIO
from threading import Lock, Thread
from timeit import default_timer as timer
from typing import Optional, Dict, Any
from fastapi import FastAPI, UploadFile, File, Form, Header
from fastapi import WebSocket, APIRouter, WebSocketDisconnect, Request, Body, Query, Depends
from fastapi.responses import StreamingResponse, JSONResponse, Response
from openai import OpenAI
chat_router = APIRouter()
# 从环境变量读取 OpenAI API 密钥
openai_api_key = os.getenv("DASHSCOPE_API_KEY")
if not openai_api_key:
raise RuntimeError("DASHSCOPE_API_KEY environment variable not set")
class MillisecondsFormatter(logging.Formatter):
"""自定义日志格式器,添加毫秒时间戳"""
def formatTime(self, record, datefmt=None):
# 将时间戳转换为本地时间元组
ct = self.converter(record.created)
# 格式化为 "小时:分钟:秒"
t = time.strftime("%H:%M:%S", ct)
# 添加毫秒3位
return f"{t}.{int(record.msecs):03d}"
# 配置全局日志格式
def configure_logging():
# 创建 Formatter
log_format = "%(asctime)s - %(levelname)s - %(message)s"
formatter = MillisecondsFormatter(log_format)
# 获取根 Logger 并清除已有配置
root_logger = logging.getLogger()
root_logger.handlers = []
# 创建并配置 Handler输出到控制台
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
# 设置日志级别并添加 Handler
root_logger.setLevel(logging.INFO)
root_logger.addHandler(console_handler)
# 调用配置函数(程序启动时运行一次)
configure_logging()
# 简单的 API 密钥验证(可选)
def verify_api_key(request: Request):
"""简单的 API 密钥验证"""
if request.headers.get("X-API-KEY") != os.getenv("APP_API_KEY", "default_key"):
raise HTTPException(status_code=401, detail="Invalid API key")
return True
@chat_router.post("/completion")
async def chat_completion(request: Request):
"""
与大语言模型进行对话
请求体示例:
{
"messages": [
{"role": "system", "content": "你是一个有帮助的助手"},
{"role": "user", "content": "你好请介绍一下FastAPI"}
],
"model": "gpt-3.5-turbo",
"temperature": 0.7,
"max_tokens": 500
}
"""
try:
# 手动解析请求体
request_data = await request.json()
# 验证必需字段
if "messages" not in request_data or not isinstance(request_data["messages"], list):
raise HTTPException(status_code=400, detail="Missing or invalid 'messages' field")
# 提取参数并提供默认值
messages = request_data["messages"]
model = request_data.get("model", "qwen-plus-latest")
temperature = float(request_data.get("temperature", 0.7))
max_tokens = int(request_data.get("max_tokens", 500))
# 验证消息结构
for msg in messages:
if "role" not in msg or "content" not in msg:
raise HTTPException(status_code=400, detail="Invalid message structure")
if not isinstance(msg["role"], str) or not isinstance(msg["content"], str):
raise HTTPException(status_code=400, detail="Message content must be strings")
logging.info(f"Received chat request: model={model}, messages={len(messages)}")
client = OpenAI(
# 若没有配置环境变量请用百炼API Key将下行替换为api_key="sk-xxx",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 调用 OpenAI API
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature
)
# 处理响应
if not response.choices:
raise HTTPException(status_code=500, detail="No response from AI model")
choice = response.choices[0]
message = choice.message
return {
"message": {
"role": message.role,
"content": message.content
},
"finish_reason": choice.finish_reason
}
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON format")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logging.exception("Internal server error")
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")

View File

@@ -5,9 +5,9 @@ from contextlib import contextmanager
from config import DATABASE_CONFIG from config import DATABASE_CONFIG
from datetime import datetime,timedelta from datetime import datetime,timedelta
import logging import logging
from typing import Optional,Union from typing import Union, List, Dict, Optional
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type
from dateutil.relativedelta import relativedelta
# 配置日志 # 配置日志
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@@ -389,7 +389,7 @@ def create_user(data: dict):
# 查询用户(多条件) # 查询用户(多条件)
def get_users(status: int = None, email: str = None, phone: str = None): def get_users(status: int = None, email: str = None, phone: str = None,openid: str = None):
base_sql = "SELECT * FROM rag_flow.users_info WHERE 1=1" base_sql = "SELECT * FROM rag_flow.users_info WHERE 1=1"
params = [] params = []
@@ -404,7 +404,10 @@ def get_users(status: int = None, email: str = None, phone: str = None):
if phone: if phone:
base_sql += " AND phone = %s" base_sql += " AND phone = %s"
params.append(phone) params.append(phone)
if openid:
base_sql += " AND openid = %s"
params.append(openid)
base_sql += " ORDER BY create_time DESC" base_sql += " ORDER BY create_time DESC"
return execute_query(base_sql, tuple(params)) return execute_query(base_sql, tuple(params))
@@ -531,4 +534,672 @@ def update_login_info(user_id: str, token: str):
) )
execute_query(sql, params) execute_query(sql, params)
return get_user_by_id(user_id) return get_user_by_id(user_id)
# database.py
#------------------------------------------------------------
def get_museum_subscriptions_by_museum(museum_id: int) -> list:
"""
获取指定博物馆的所有有效订阅套餐
功能说明:
- 查询指定博物馆的所有可用订阅套餐
- 返回结果包含博物馆订阅信息和关联的模板信息
参数说明:
- museum_id: 博物馆ID
返回:
- 包含订阅信息的字典列表
重要逻辑:
- 只返回 is_active=1 的有效订阅
- 通过 JOIN 关联 subscription_templates 表获取模板信息
- 结果按有效期类型排序,便于前端展示
"""
sql = """
SELECT
ms.id,
ms.museum_id,
ms.template_id,
ms.price,
ms.sub_id,
ms.is_active,
ms.created_date,
ms.updated_date,
st.name AS template_name,
st.description AS template_description,
st.validity_type
FROM museum_subscriptions ms
JOIN subscription_templates st ON ms.template_id = st.id
WHERE ms.museum_id = %s AND ms.is_active = 1
ORDER BY st.validity_type
"""
return execute_query(sql, (museum_id,))
def get_museum_subscription_by_id(subscription_id: str) -> dict:
"""
根据ID获取博物馆订阅套餐的详细信息
功能说明:
- 通过订阅ID获取完整的订阅信息
- 包含关联的模板信息
参数说明:
- subscription_id: 博物馆订阅ID
返回:
- 订阅信息的字典如果不存在则返回None
重要逻辑:
- 使用内连接获取模板信息
- 确保返回完整的订阅+模板数据
"""
sql = """
SELECT
ms.*,
st.name AS template_name,
st.description AS template_description,
st.validity_type
FROM museum_subscriptions ms
JOIN subscription_templates st ON ms.template_id = st.id
WHERE ms.sub_id = %s
"""
result = execute_query(sql, (subscription_id,))
return result[0] if result else None
def create_order(order_data: dict) -> int:
"""
创建新的订阅订单
功能说明:
- 在 subscription_orders 表中插入新订单记录
参数说明:
- order_data: 包含订单数据的字典,字段包括:
order_id: 订单号 (必需)
user_id: 用户ID (必需)
museum_subscription_id: 博物馆订阅ID (必需)
amount: 订单金额 (默认0.00)
status: 订单状态 (默认'created')
transaction_id: 支付交易号 (可选)
create_date: 创建时间 (默认当前时间)
pay_time: 支付时间 (可选)
返回:
- 执行结果的行数
重要逻辑:
- 为可选字段提供默认值
- 使用参数化查询防止SQL注入
- 处理所有必需的订单字段
"""
sql = """
INSERT INTO subscription_orders (
order_id,
user_id,
museum_subscription_id,
amount,
status,
transaction_id,
create_date,
pay_time
) VALUES (
%s, %s, %s, %s, %s, %s, %s, %s
)
"""
# 设置默认值
params = (
order_data.get("order_id"),
order_data.get("user_id"),
order_data.get("museum_subscription_id"),
order_data.get("amount", 0.00),
order_data.get("status", "created"),
order_data.get("transaction_id"),
order_data.get("create_date", datetime.now()),
order_data.get("pay_time")
)
return execute_query(sql, params, autocommit=True)
def update_order(order_id: str, update_data: dict) -> int:
"""
更新订单信息
功能说明:
- 动态更新订单的字段
- 只更新提供的字段
参数说明:
- order_id: 要更新的订单ID
- update_data: 包含更新字段的字典,可选字段包括:
status: 订单状态
transaction_id: 支付交易号
amount: 订单金额
pay_time: 支付时间
返回:
- 受影响的行数
重要逻辑:
- 只允许更新预定义的字段
- 防止更新不允许的字段
- 使用参数化查询确保安全
"""
# 定义允许更新的字段及其SQL部分
allowed_fields = {
'status': 'status = %s',
'transaction_id': 'transaction_id = %s',
'amount': 'amount = %s',
'pay_time': 'pay_time = %s'
}
update_fields = []
params = []
# 收集要更新的字段
for field, sql_part in allowed_fields.items():
if field in update_data:
update_fields.append(sql_part)
params.append(update_data[field])
# 如果没有提供有效更新字段,直接返回
if not update_fields:
return 0
# 构建动态SQL
set_clause = ", ".join(update_fields)
sql = f"UPDATE subscription_orders SET {set_clause} WHERE order_id = %s"
params.append(order_id)
return execute_query(sql, tuple(params), autocommit=True)
from typing import Union, List, Dict, Optional
def get_order_by_id(order_id: str = None, user_id: str = None,combined = None) -> Union[Dict, List[Dict], None]:
"""
根据订单ID或用户ID查询订单信息
功能说明:
- 支持通过 order_id 或 user_id 查询订单信息
- 当传入 order_id 时,返回单个订单(字典)
- 当传入 user_id 时,返回该用户的所有订单(列表)
参数说明:
- order_id: 订单号(字符串)
- user_id: 用户ID字符串
返回:
- 如果传入 order_id: 返回单个订单的字典(如果存在)
- 如果传入 user_id: 返回该用户的所有订单列表(可能为空)
- 如果两个参数都未传入,返回 None
重要逻辑:
- 使用参数化查询防止 SQL 注入
- 当同时传入 order_id 和 user_id 时,优先使用 order_id
"""
if not order_id and not user_id:
return None # 两个参数都未传入,直接返回 None
sql_wo_condition= """
SELECT
o.order_id,
o.user_id,
u.phone,
u.openid,
o.museum_subscription_id AS subscription_id,
ms.museum_id,
ms.template_id,
t.name AS template_name,
t.description AS template_desc,
t.validity_type,
ms.price AS subscription_price,
o.amount AS order_amount,
o.status AS order_status,
o.transaction_id,
o.create_date AS order_create_time,
o.pay_time,
us.start_date,
us.end_date,
us.is_active AS subscription_active,
mo.name AS museum_name
FROM
rag_flow.subscription_orders o
LEFT JOIN rag_flow.users_info u ON o.user_id = u.user_id
LEFT JOIN rag_flow.museum_subscriptions ms ON o.museum_subscription_id = ms.sub_id
LEFT JOIN rag_flow.subscription_templates t ON ms.template_id = t.id
LEFT JOIN rag_flow.user_subscriptions us ON o.order_id = us.order_id
LEFT JOIN rag_flow.mesum_overview mo ON ms.museum_id = mo.id
"""
# 优先使用 order_id 查询
if order_id and not combined:
sql = "SELECT * FROM subscription_orders WHERE order_id = %s"
result = execute_query(sql, (order_id,))
return result[0] if result and len(result) > 0 else None # 返回单个订单
# 如果 order_id 不存在,使用 user_id 查询
if user_id and not combined:
sql = "SELECT * FROM subscription_orders WHERE user_id = %s"
result = execute_query(sql, (user_id,))
return result if result else [] # 返回所有订单(列表)
if user_id and combined:
sql = sql_wo_condition + f"\n WHERE o.user_id = %s"
result = execute_query(sql, (user_id,))
return result if result else [] # 返回所有订单(列表)
if order_id and combined:
sql = sql_wo_condition + f"\n WHERE o.order_id = %s"
result = execute_query(sql, (order_id,))
return result[0] if result and len(result) > 0 else None # 返回单个订单
def create_user_subscription(data: dict) -> int:
"""
创建用户订阅记录
功能说明:
- 在 user_subscriptions 表中插入新记录
- 表示用户购买并激活了一个订阅
参数说明:
- data: 包含订阅数据的字典,字段包括:
user_id: 用户ID (必需)
museum_subscription_id: 博物馆订阅ID (必需)
order_id: 关联的订单ID (必需)
start_date: 开始时间 (默认当前时间)
end_date: 结束时间 (必需)
is_active: 是否激活 (默认1)
create_date: 创建时间 (默认当前时间)
返回:
- 执行结果的行数
重要逻辑:
- 为可选字段提供默认值
- 确保所有必需字段都有值
- 处理时间字段的默认值
"""
sql = """
INSERT INTO user_subscriptions (
user_id,
museum_subscription_id,
order_id,
start_date,
end_date,
is_active,
create_date
) VALUES (
%s, %s, %s, %s, %s, %s, %s
)
"""
# 设置默认值
params = (
data.get("user_id"),
data.get("museum_subscription_id"),
data.get("order_id"),
data.get("start_date", datetime.now()),
data.get("end_date"),
data.get("is_active", 1),
data.get("create_date", datetime.now())
)
return execute_query(sql, params, autocommit=True)
def deactivate_previous_subscriptions(user_id: str, museum_subscription_id: str) -> int:
"""
禁用用户在同一博物馆的旧订阅
功能说明:
- 将用户在同一博物馆的所有激活订阅设为非激活状态
- 确保同一博物馆只有一个激活订阅
参数说明:
- user_id: 用户ID
- museum_id: 博物馆ID
返回:
- 受影响的行数
重要逻辑:
- 通过JOIN关联博物馆订阅表
- 只更新同一博物馆的订阅
- 保持历史订阅记录,只修改激活状态
"""
sql = """
UPDATE user_subscriptions us
JOIN museum_subscriptions ms ON us.museum_subscription_id = ms.sub_id
SET us.is_active = 0
WHERE us.user_id = %s AND us.museum_subscription_id = %s AND us.is_active = 1
"""
return execute_query(sql, (user_id, museum_subscription_id), autocommit=True)
def get_user_by_id(user_id: str) -> dict:
"""
根据用户ID获取用户信息
功能说明:
- 通过用户ID查询用户基本信息
参数说明:
- user_id: 用户ID
返回:
- 用户信息的字典如果不存在则返回None
重要逻辑:
- 直接查询用户表的所有字段
"""
sql = "SELECT * FROM users_info WHERE user_id = %s"
result = execute_query(sql, (user_id,))
return result[0] if result else None
def get_subscription_template_by_id(template_id: int) -> dict:
"""
根据模板ID获取订阅模板信息
功能说明:
- 通过模板ID查询订阅模板详情
参数说明:
- template_id: 模板ID
返回:
- 模板信息的字典如果不存在则返回None
"""
sql = "SELECT * FROM subscription_templates WHERE id = %s"
result = execute_query(sql, (template_id,))
return result[0] if result else None
def get_active_user_subscription(user_id: str, museum_id: int) -> dict:
"""
获取用户在指定博物馆的有效订阅
功能说明:
- 查询用户在特定博物馆的当前有效订阅
- 有效订阅定义为: 已激活且未过期
参数说明:
- user_id: 用户ID
- museum_id: 博物馆ID
返回:
- 订阅信息的字典如果不存在则返回None
重要逻辑:
- 通过JOIN关联博物馆订阅表
- 检查 is_active=1 和 end_date > NOW()
- 返回最新到期的订阅
"""
sql = """
SELECT us.*
FROM user_subscriptions us
JOIN museum_subscriptions ms ON us.museum_subscription_id = ms.id
WHERE us.user_id = %s
AND ms.museum_id = %s
AND us.is_active = 1
AND us.end_date > NOW()
ORDER BY us.end_date DESC
LIMIT 1
"""
result = execute_query(sql, (user_id, museum_id))
return result[0] if result else None
def get_user_subscription_history(user_id: str) -> list:
"""
获取用户的订阅历史记录
功能说明:
- 查询用户的所有订阅记录(包括历史和当前)
- 返回完整的订阅详情,包含博物馆和模板信息
参数说明:
- user_id: 用户ID
返回:
- 包含订阅历史记录的字典列表
重要逻辑:
- 通过多层JOIN关联所有相关表
- 包含博物馆名称、模板信息等
- 按开始时间倒序排列,最新订阅在前
"""
sql = """
SELECT
us.*,
ms.price,
ms.sub_id,
st.name AS template_name,
st.description AS template_description,
st.validity_type,
mo.name AS museum_name
FROM user_subscriptions us
JOIN museum_subscriptions ms ON us.museum_subscription_id = ms.id
JOIN subscription_templates st ON ms.template_id = st.id
JOIN mesum_overview mo ON ms.museum_id = mo.id
WHERE us.user_id = %s
ORDER BY us.start_date DESC
"""
return execute_query(sql, (user_id,))
def get_user_subscription_by_order(order_id: str) -> dict:
"""
根据订单ID获取用户订阅信息
功能说明:
- 通过订单ID查询关联的用户订阅
参数说明:
- order_id: 订单ID
返回:
- 订阅信息的字典如果不存在则返回None
重要逻辑:
- 用于支付回调后验证订阅是否已创建
"""
sql = "SELECT * FROM user_subscriptions WHERE order_id = %s"
result = execute_query(sql, (order_id,))
return result[0] if result else None
def activate_free_subscription(user_id: str, museum_id: int) -> str:
"""
激活免费订阅
功能说明:
- 为用户在指定博物馆激活免费订阅
- 创建订单记录和订阅记录
参数说明:
- user_id: 用户ID
- museum_id: 博物馆ID
返回:
- 创建的订单ID
重要逻辑:
- 查找博物馆的免费订阅
- 创建订单记录 (状态为activated)
- 创建订阅记录 (有效期为7天)
"""
# 1. 获取博物馆的免费订阅
sql = """
SELECT ms.id
FROM museum_subscriptions ms
JOIN subscription_templates st ON ms.template_id = st.id
WHERE ms.museum_id = %s
AND st.validity_type = 'free'
AND ms.is_active = 1
LIMIT 1
"""
result = execute_query(sql, (museum_id,))
if not result:
raise ValueError("该博物馆没有可用的免费订阅")
subscription_id = result[0]["id"]
# 2. 创建免费订单
order_id = f"FREE_{int(time.time())}"
create_order({
"order_id": order_id,
"user_id": user_id,
"museum_subscription_id": subscription_id,
"amount": 0,
"status": "activated",
"create_date": datetime.now()
})
# 3. 创建用户订阅记录 (免费订阅有效期为7天)
start_date = datetime.now()
end_date = start_date + timedelta(days=7)
create_user_subscription({
"user_id": user_id,
"museum_subscription_id": subscription_id,
"order_id": order_id,
"start_date": start_date,
"end_date": end_date,
"is_active": True
})
# 4. 禁用同一博物馆的旧订阅
deactivate_previous_subscriptions(user_id, subscription_id)
return order_id
def activate_user_subscription(
user_id: str,
museum_subscription_id: str,
order_id: str
) -> bool:
"""
激活用户订阅服务
参数:
- user_id: 用户ID
- museum_subscription_id: 博物馆订阅套餐ID
- order_id: 关联的订单ID
返回:
- 激活成功返回True失败返回False
主要逻辑:
1. 获取博物馆订阅信息
2. 禁用同一博物馆的旧订阅
3. 计算订阅有效期
4. 创建用户订阅记录
5. 处理重复激活和并发请求
"""
try:
# 1. 获取博物馆订阅信息
museum_sub = get_museum_subscription_by_id(museum_subscription_id)
if not museum_sub:
logger.error(f"博物馆订阅不存在: {museum_subscription_id}")
return False
# 2. 获取关联的订阅模板
template = get_subscription_template_by_id(museum_sub["template_id"])
if not template:
logger.error(f"订阅模板不存在: {museum_sub['template_id']}")
return False
# 3. 禁用同一博物馆的旧订阅
deactivated_count = deactivate_previous_subscriptions(
user_id=user_id,
museum_subscription_id=museum_subscription_id
)
logger.info(f"已禁用{deactivated_count}个同一博物馆的旧订阅")
# 4. 计算订阅有效期
start_date = datetime.now()
end_date = calculate_subscription_expiry(start_date,template["validity_type"])
# 5. 检查是否已激活过(防止重复激活)
existing_sub = get_user_subscription_by_order(order_id)
if existing_sub:
logger.warning(f"订阅已激活过,跳过重复激活. Order: {order_id}")
return True
# 6. 创建用户订阅记录
subscription_data = {
"user_id": user_id,
"museum_subscription_id": museum_subscription_id,
"order_id": order_id,
"start_date": start_date,
"end_date": end_date,
"is_active": 1
}
create_user_subscription(subscription_data)
logger.info(f"订阅激活成功. 用户: {user_id}, 套餐: {museum_subscription_id}, "
f"有效期: {start_date}{end_date}")
return True
except Exception as e:
logger.exception(f"激活订阅失败: {str(e)}")
return False
def check_user_subscription(user_id: str, museum_id: int) -> dict:
"""
检查用户是否拥有指定博物馆的有效订阅
功能说明:
- 检查用户是否有指定博物馆的未过期激活订阅
参数说明:
- user_id: 用户ID
- museum_id: 博物馆ID
返回:
- 订阅信息字典如果没有则返回None
重要逻辑:
- 优先检查当前有效的订阅
- 如果没有,检查免费订阅是否可用
"""
# 1. 检查当前有效订阅
active_sub = get_active_user_subscription(user_id, museum_id)
if active_sub:
return active_sub
# 2. 检查是否有免费订阅可用
# (这里可以扩展更多逻辑,如试用期检查等)
return None
def calculate_subscription_expiry(start_date: datetime, validity_type: str) -> datetime:
"""
根据有效期类型计算到期日期
参数:
- start_date: 订阅开始日期
- validity_type: 有效期类型 (free, 1month, 1year, permanent)
返回:
- 到期日期
"""
if validity_type == "free":
# 免费套餐通常有较短有效期例如7天
return start_date + timedelta(days=7)
elif validity_type == "1month":
# 下个月的同一天(自动处理月末情况)
return start_date + relativedelta(months=1)
elif validity_type == "1year":
# 下一年的同一天
return start_date + relativedelta(years=1)
elif validity_type == "permanent":
# 永久有效设置为100年后
return start_date + relativedelta(years=100)
else:
# 未知类型默认30天
logger.warning(f"未知有效期类型: {validity_type}, 使用默认30天")
return start_date + timedelta(days=30)

View File

@@ -1,12 +1,12 @@
from fastapi import WebSocket, APIRouter,WebSocketDisconnect,Request,Body,Query from fastapi import WebSocket, APIRouter,WebSocketDisconnect,Request,Body,Query
from fastapi import FastAPI, UploadFile, File, Form, Header,Depends from fastapi import FastAPI, UploadFile, File, Form, Header, Depends
from fastapi.responses import StreamingResponse,JSONResponse from fastapi.responses import StreamingResponse,JSONResponse
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt from jose import JWTError, jwt
import logging import logging
from fastapi import HTTPException from fastapi import HTTPException
from Crypto.Cipher import AES from Crypto.Cipher import AES
import base64,uuid import base64,uuid,asyncio
import requests import requests
from datetime import datetime,timedelta from datetime import datetime,timedelta
from database import * from database import *
@@ -17,10 +17,10 @@ logger = logging.getLogger("login")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # tokenUrl 对应登录接口路径 oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # tokenUrl 对应登录接口路径
# 需要配置的参数(从环境变量获取) # 需要配置的参数(从环境变量获取)
WX_APPID = "wxed388cef83f109a3" # 小程序appid WX_APPID = "wx446813bfb3a6985a" #"wxed388cef83f109a3" # 小程序appid
WX_SECRET = "f687afd2c8fae49b4aed2e4a8dd76e6e" # 小程序密钥 WX_SECRET = "a7455fca777ad59ce96cc154d62f795f" #"f687afd2c8fae49b4aed2e4a8dd76e6e" # 小程序密钥
WX_API_URL = "https://api.weixin.qq.com/sns/jscode2session" WX_API_URL = "https://api.weixin.qq.com/sns/jscode2session"
JWT_SECRET_KEY="3e5b8d7f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d" JWT_SECRET_KEY = "3e5b8d7f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d"
ALGORITHM = "HS256" ALGORITHM = "HS256"
# 伪数据库 # 伪数据库
@@ -45,7 +45,7 @@ def create_jwt(user_id: str) -> str:
return jwt.encode(payload, JWT_SECRET_KEY, algorithm=ALGORITHM) return jwt.encode(payload, JWT_SECRET_KEY, algorithm=ALGORITHM)
def decrypt_phone(encrypted_data: str, session_key: str, iv: str) -> dict: def decrypt_data(encrypted_data: str, session_key: str, iv: str) -> dict:
try: try:
# Base64解码 # Base64解码
@@ -65,15 +65,17 @@ def decrypt_phone(encrypted_data: str, session_key: str, iv: str) -> dict:
# 解析JSON # 解析JSON
result = json.loads(decrypted.decode('utf-8')) result = json.loads(decrypted.decode('utf-8'))
if 'purePhoneNumber' not in result: logging.info(f"解密数据: {result}")
raise ValueError("解密数据不包含手机号") return result
except json.JSONDecodeError:
return result['purePhoneNumber'] # 特定错误类型识别
logging.info(f"解密过程失败: {str(e)}")
raise ValueError("SESSION_KEY_MISMATCH")
except Exception as e: except Exception as e:
raise ValueError(f"解密过程失败: {str(e)}") logging.info(f"解密过程失败: {str(e)}")
except Exception as e: if "padding" in str(e).lower():
raise ValueError(f"解密失败: {str(e)}") raise ValueError("SESSION_KEY_EXPIRED")
raise
async def get_wx_session(code: str): async def get_wx_session(code: str):
""" """
@@ -124,7 +126,6 @@ async def get_wx_session(code: str):
status_code=401, status_code=401,
detail="微信认证失败缺少openid" detail="微信认证失败缺少openid"
) )
logging.info(f"wx_data {wx_data}")
return wx_data return wx_data
@@ -140,24 +141,61 @@ async def wechat_login(request: Request):
if not all(k in data for k in required_fields): if not all(k in data for k in required_fields):
raise HTTPException(400, "Missing required fields") raise HTTPException(400, "Missing required fields")
code = data.get('code') code = data.get('code')
# 调用微信接口获取session信息 encrypted_data = data['encryptedData']
try: iv = data['iv']
wx_data = await get_wx_session(code)
logging.info(f"wechat login wx_data={wx_data}")
except HTTPException as e:
raise e # 直接抛出已处理过的异常
# 伪解密手机号 logging.info(f"wechat login data={data}")
try: # 关键修改:增加重试机制
#phone_number = "138" + str(hash(data["encryptedData"]))[-8:] max_retries = 2
phone_number=decrypt_phone(data["encryptedData"],wx_data['session_key'],data["iv"]) for attempt in range(max_retries):
except: try:
raise HTTPException(400, "Decrypt failed") # 每次尝试都重新获取 session_key
# 微信小程序登录 code 具有一次性特征有效期约5分钟
# 添加冷启动保护延迟
await asyncio.sleep(0.5) # 关键延迟
logging.info(f"decrypt_phone return {phone_number}") wx_data = await get_wx_session(code)
session_key = wx_data["session_key"]
openid = wx_data["openid"]
logging.info(f"get_wx_session return {wx_data}")
# 解密数据
try:
result = decrypt_data(encrypted_data, session_key, iv)
except ValueError as e:
# 特定错误处理
if "SESSION_KEY_" in str(e):
raise HTTPException(418, "SESSION_KEY_INVALID")
raise
if 'purePhoneNumber' not in result:
logging.warning("解密数据不包含手机号")
phone_number = None
else:
phone_number = result['purePhoneNumber']
break # 成功则跳出循环
except HTTPException as e:
if attempt < max_retries - 1:
# 特定错误时刷新 code
if "invalid session_key" in str(e).lower():
logging.warning("Session key 过期,尝试刷新")
# 触发前端重新获取 code
raise HTTPException(401, "SESSION_KEY_EXPIRED")
else:
logging.error(f"尝试 {attempt + 1} 失败: {str(e)}")
await asyncio.sleep(1) # 短暂等待后重试
else:
logging.error(f"最终解密失败: {str(e)}")
raise HTTPException(400, "Decrypt failed")
except Exception as e:
logging.error(f"解密异常: {str(e)}")
if attempt == max_retries - 1:
raise HTTPException(400, "Decrypt failed")
logging.info(f"decrypt_data return {phone_number}")
# ========== 数据库操作开始 ========== # ========== 数据库操作开始 ==========
# 使用数据库查询替代内存查询 # 使用数据库查询替代内存查询
db_users = get_users(phone=phone_number) db_users = get_users(openid=openid)
user = db_users[0] if db_users else None user = db_users[0] if db_users else None
# 用户不存在时创建新用户 # 用户不存在时创建新用户
@@ -184,20 +222,8 @@ async def wechat_login(request: Request):
} }
updated_user = update_user(user["user_id"], update_data) updated_user = update_user(user["user_id"], update_data)
# ========== 数据库操作结束 ========== # ========== 数据库操作结束 ==========
"""
# 查找或创建用户
user = next((u for u in fake_db["users"] if u["phone"] == phone_number), None)
if not user:
user = {
"id": str(uuid.uuid4()),
"openid": wx_data["openid"],
"phone": phone_number,
"museums": [1, 2, 3] # 伪权限数据
}
fake_db["users"].append(user)
logging.info(f"login return {user}")
"""
logging.info(f"login return {user}")
# 生成token # 生成token
return JSONResponse({ return JSONResponse({
"token": create_jwt(user["user_id"]), "token": create_jwt(user["user_id"]),
@@ -302,32 +328,6 @@ async def optional_current_user(token: str = Depends(oauth2_scheme)):
except (JWTError, StopIteration): except (JWTError, StopIteration):
return None return None
async def get_current_user(token: str = Depends(oauth2_scheme)) :
# 身份验证核心逻辑
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭证",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# 1. 解码并验证JWT
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
# 2. 从数据库获取完整用户信息
user = get_user_by_id(user_id)
if user is None:
raise credentials_exception
return user
except JWTError:
raise credentials_exception
@login_router.get("/verify") @login_router.get("/verify")
async def verify_token(user: dict = Depends(optional_current_user)): async def verify_token(user: dict = Depends(optional_current_user)):
""" """

View File

@@ -1,3 +1,4 @@
import os
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
@@ -6,10 +7,10 @@ from jose import JWTError, jwt
from datetime import datetime, timedelta from datetime import datetime, timedelta
import json import json
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from app.asr_service import asr_router
from app.monitor_service import monitor_router from dotenv import load_dotenv
from app.tts_service import tts_router
from app.login_service import login_router
import uvicorn import uvicorn
@asynccontextmanager @asynccontextmanager
@@ -25,6 +26,16 @@ async def lifespan(app: FastAPI):
print(" Service Stopped Cleanly") print(" Service Stopped Cleanly")
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
# 加载 .env 文件中的环境变量
load_dotenv() # 默认加载项目根目录的 .env 文件
from app.asr_service import asr_router
from app.monitor_service import monitor_router
from app.tts_service import tts_router
from app.login_service import login_router
from app.chat_service import chat_router
from app.payment_service import payment_router
# 创建应用实例 # 创建应用实例
app = FastAPI(lifespan=lifespan) app = FastAPI(lifespan=lifespan)
@@ -35,6 +46,7 @@ app.add_middleware(
allow_credentials=True, # 是否允许发送 cookies allow_credentials=True, # 是否允许发送 cookies
allow_methods=["*"], # 允许所有方法,也可以指定具体的方法,例如 ["GET", "POST"] allow_methods=["*"], # 允许所有方法,也可以指定具体的方法,例如 ["GET", "POST"]
allow_headers=["*"], # 允许所有头信息,也可以指定具体的头信息 allow_headers=["*"], # 允许所有头信息,也可以指定具体的头信息
expose_headers=["Content-Range", "Content-Length"] # 关键添加
) )
# 挂载子路由 # 挂载子路由
@@ -42,6 +54,8 @@ app.include_router(asr_router, prefix="/asr")
app.include_router(monitor_router, prefix="/monitor") app.include_router(monitor_router, prefix="/monitor")
app.include_router(tts_router, prefix="/tts") app.include_router(tts_router, prefix="/tts")
app.include_router(login_router, prefix="/auth") app.include_router(login_router, prefix="/auth")
app.include_router(chat_router, prefix="/chat")
app.include_router(payment_router, prefix="/payment")
# 挂载静态文件(可选) # 挂载静态文件(可选)
# app.mount("/static", StaticFiles(directory="static"), name="static") # app.mount("/static", StaticFiles(directory="static"), name="static")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

263
asr-monitor-test/backup.txt Normal file
View File

@@ -0,0 +1,263 @@
"""
ALI_KEY = "sk-a47a3fb5f4a94f66bbaf713779101c75"
class QwenTTS:
def __init__(self, key,format="mp3",sample_rate=44100, model_name="cosyvoice-v1/longxiaochun"):
import dashscope
print("---begin--init dialog_service QwenTTS--") # cyx
self.model_name = model_name
dashscope.api_key = key
self.synthesizer = None
self.callback = None
self.is_cosyvoice = False
self.voice = ""
self.format = format
self.sample_rate = sample_rate
if '/' in model_name:
parts = model_name.split('/', 1)
# 返回分离后的两个字符串parts[0], parts[1]
if parts[0] == 'cosyvoice-v1':
self.is_cosyvoice = True
self.voice = parts[1]
def init_streaming_call(self, audio_call_back):
from dashscope.api_entities.dashscope_response import SpeechSynthesisResponse
# cyx 2025 01 19 测试cosyvoice 使用tts_v2 版本
from dashscope.audio.tts_v2 import ResultCallback, SpeechSynthesizer, AudioFormat # , SpeechSynthesisResult
from dashscope.audio.tts import SpeechSynthesisResult
from collections import deque
print(f"--QwenTTS--tts_stream begin-- {self.is_cosyvoice} {self.voice}") # cyx
class Callback_v2(ResultCallback):
def __init__(self) -> None:
self.dque = deque()
def _run(self):
while True:
if not self.dque:
time.sleep(0)
continue
val = self.dque.popleft()
if val:
yield val
else:
break
def on_open(self):
logging.info("Qwen tts open")
pass
def on_complete(self):
self.dque.append(None)
def on_error(self, response: SpeechSynthesisResponse):
print("Qwen tts error", str(response))
raise RuntimeError(str(response))
def on_close(self):
# print("---Qwen call back close") # cyx
logging.info("Qwen tts close")
pass
# canceled for test 语音大模型CosyVoice
#def on_event(self, result: SpeechSynthesisResult):
# if result.get_audio_frame() is not None:
# self.dque.append(result.get_audio_frame())
#
def on_event(self, message):
# print(f"recv speech synthsis message {message}")
pass
# 以下适合语音大模型CosyVoice
def on_data(self, data: bytes) -> None:
if len(data) > 0:
#self.dque.append(data)
if audio_call_back:
audio_call_back(data)
# --------------------------
# text = self.normalize_text(text)
try:
self.callback = Callback_v2()
format =self.get_audio_format(self.format,self.sample_rate)
self.synthesizer = SpeechSynthesizer(
model='cosyvoice-v1',
# voice="longyuan", #"longfei",
voice=self.voice,
callback=self.callback,
format=format
)
#self.synthesizer.call(text)
except Exception as e:
print(f"---dale---20 error {e}") # cyx
# -----------------------------------
# print(f"---Qwen return data {num_tokens_from_string(text)}")
# yield num_tokens_from_string(text)
except Exception as e:
raise RuntimeError(f"**ERROR**: {e}")
def get_audio_format(self, format: str, sample_rate: int):
# 动态获取音频格式
from dashscope.audio.tts_v2 import AudioFormat
logging.info(f"QwenTTS--get_audio_format-- {format} {sample_rate}")
format_map = {
(8000, 'mp3'): AudioFormat.MP3_8000HZ_MONO_128KBPS,
(8000, 'pcm'): AudioFormat.PCM_8000HZ_MONO_16BIT,
(8000, 'wav'): AudioFormat.WAV_8000HZ_MONO_16BIT,
(16000, 'pcm'): AudioFormat.PCM_16000HZ_MONO_16BIT,
(22050, 'mp3'): AudioFormat.MP3_22050HZ_MONO_256KBPS,
(22050, 'pcm'): AudioFormat.PCM_22050HZ_MONO_16BIT,
(22050, 'wav'): AudioFormat.WAV_22050HZ_MONO_16BIT,
(44100, 'mp3'): AudioFormat.MP3_44100HZ_MONO_256KBPS,
(44100, 'pcm'): AudioFormat.PCM_44100HZ_MONO_16BIT,
(44100, 'wav'): AudioFormat.WAV_44100HZ_MONO_16BIT,
(48800, 'mp3'): AudioFormat.MP3_48000HZ_MONO_256KBPS,
(48800, 'pcm'): AudioFormat.PCM_48000HZ_MONO_16BIT,
(48800, 'wav'):AudioFormat.WAV_48000HZ_MONO_16BIT
}
return format_map.get((sample_rate, format), AudioFormat.MP3_16000HZ_MONO_128KBPS)
def streaming_call(self,text):
if self.synthesizer:
self.synthesizer.streaming_call(text)
def end_streaming_call(self):
if self.synthesizer:
self.synthesizer.streaming_complete()
class StreamSessionManager1:
def __init__(self):
self.sessions = {} # {session_id: {'tts_model': obj, 'buffer': queue, 'task_queue': Queue}}
self.lock = threading.Lock()
self.executor = ThreadPoolExecutor(max_workers=30) # 固定大小线程池
self.gc_interval = 300 # 5分钟清理一次 5 x 60 300秒
self.gc_tts = 10 # 10s 大模型开始输出文本有可能需要比较久2025年5 24 从3s->10s
self.inited = False
self.tts_model = None
def create_session(self, tts_model,sample_rate =8000, stream_format='mp3'):
session_id = str(uuid.uuid4())
with self.lock:
self.sessions[session_id] = {
'tts_model': tts_model,
'buffer': queue.Queue(maxsize=300), # 线程安全队列
'task_queue': queue.Queue(),
'active': True,
'last_active': time.time(),
'audio_chunk_count':0,
'finished': threading.Event(), # 添加事件对象
'sample_rate':sample_rate,
'stream_format':stream_format,
"tts_chunk_data_valid":False
}
# 启动任务处理线程
threading.Thread(target=self._process_tasks, args=(session_id,), daemon=True).start()
return session_id
def append_text(self, session_id, text):
with self.lock:
session = self.sessions.get(session_id)
if not session: return
# 将文本放入任务队列(非阻塞)
#logging.info(f"StreamSessionManager append_text {text}")
try:
session['task_queue'].put(text, block=False)
except queue.Full:
logging.warning(f"Session {session_id} task queue full")
def _process_tasks(self, session_id):
#任务处理线程(每个会话独立)
session = self.sessions.get(session_id)
def audio_call_back(chunk):
logging.info(f"audio_call_back {len(chunk)}")
if session['stream_format'] == 'wav':
if first_chunk:
chunk_len = len(chunk)
if chunk_len > 2048:
session['buffer'].put(audio_fade_in(chunk, 1024))
else:
session['buffer'].put(audio_fade_in(chunk, chunk_len))
first_chunk = False
else:
session['buffer'].put(chunk)
else:
session['buffer'].put(chunk)
session['last_active'] = time.time()
session['audio_chunk_count'] = session['audio_chunk_count'] + 1
if session['tts_chunk_data_valid'] is False:
session['tts_chunk_data_valid'] = True # 20250510 增加表示连接TTS后台已经返回可以通知前端了
while True:
if not session or not session['active']:
break
if not self.inited:
self.inited = True
self.tts_model = QwenTTS(ALI_KEY, session['stream_format'], session['sample_rate'])
self.tts_model.init_streaming_call(audio_call_back)
try:
#logging.info(f"StreamSessionManager _process_tasks {session['task_queue'].qsize()}")
# 合并多个文本块最多等待50ms
texts = []
while len(texts) < 5: # 最大合并5个文本块
try:
text = session['task_queue'].get(timeout=0.1)
#logging.info(f"StreamSessionManager _process_tasks --0 {len(texts)}")
texts.append(text)
except queue.Empty:
break
if texts:
session['last_active'] = time.time() # 如果有处理文本,重置活跃时间
# 提交到线程池处理
future=self.executor.submit(
self._generate_audio,
session_id,
' '.join(texts) # 合并文本减少请求次数
)
future.result() # 等待转换任务执行完毕
session['last_active'] = time.time()
# 会话超时检查
if time.time() - session['last_active'] > self.gc_interval:
self.close_session(session_id)
break
if time.time() - session['last_active'] > self.gc_tts:
session['finished'].set()
if self.tts_model:
self.tts_model.end_streaming_call()
break
except Exception as e:
logging.error(f"Task processing error: {str(e)}")
def _generate_audio(self, session_id, text):
#实际生成音频(线程池执行)
session = self.sessions.get(session_id)
if not session: return
# logging.info(f"_generate_audio:{text}")
first_chunk = True
try:
self.tts_model.streaming_call(text)
except Exception as e:
session['buffer'].put(f"ERROR:{str(e)}")
logging.info(f"--streaming_call--error {str(e)}")
def close_session(self, session_id):
with self.lock:
if session_id in self.sessions:
# 标记会话为不活跃
self.sessions[session_id]['active'] = False
# 延迟2秒后清理资源
threading.Timer(1, self._clean_session, args=[session_id]).start()
def _clean_session(self, session_id):
with self.lock:
if session_id in self.sessions:
del self.sessions[session_id]
def get_session(self, session_id):
return self.sessions.get(session_id)
"""

Binary file not shown.

View File

@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEKzCCAxOgAwIBAgIUYpLQpNewkuNh1t0iwv14MUedSNkwDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjUwNzA3MjMwMTM4WhcNMzAwNzA2MjMwMTM4WjCBhDETMBEGA1UEAwwK
MTcyMTMwMTAwNjEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTAwLgYDVQQL
DCfljY7lsI/ljZrvvIjljJfkuqzvvInnp5HmioDmnInpmZDlhazlj7gxCzAJBgNV
BAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBANp0L8eLiwxVMPWRWxEWbfLgg7V91CHIM09EnvepPIdgNfUdtBcv
89XVBjs+1iNhv/74YgW01xgZQbmYCVFmUSme8LzZx7gIFqZH0Hm5CNGET+IGNLIs
ENkI8I+4N0fRsYddFJEqQzkpFMFQACxVldna9fO57jTuJtNCFD97x450S6BEsriP
OmvsVJTwrS+oZx+Qkme8HDvmeeQUOxNuWfvxyc1wgCrOzgob3iVT4wd7eKjqQm0v
r3W8ptD5KMUsqJrBy5s75RGaeDyEJaVVv04tk4kQQ/Yj7RWZChxPExYlqotJAbE4
jZT0Hfs4Z/0lSbutGlVofJhQCuZ7sH6E/KUCAwEAAaOBuTCBtjAJBgNVHRMEAjAA
MAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2
Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJD
MDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJC
MjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBC
KwLHbLPFH2N7ge8SMVaSj/4wd2Yak0dC/786Rv+dHlVlOKhpEZuCRtIe+3D/PJWX
tdPZwjMICDSKMbbNt+UW4IPrpQeWqc3M8vx1nS3k/iYpOBNoy2e0IKhJi1inOlWq
4iRSpUIG/EKqPkjDs6GouROznzYXY85RNmVrCH/k19So4eSNrYe6D30rkQfklYGO
PUJMCvjZrcNrZa0Ka/xAI9VHRB814WXDvnBxEDaGHena8SQpPs5p0ikT0C5sVYt8
QQ8D+ZZe7gkod5dknt8X8XXFSAMxYko4+O/aUIMql76kMFcw0XcDQ8G8miRhhMHJ
DTTchZFuZnYvYYuzl9JN
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDadC/Hi4sMVTD1
kVsRFm3y4IO1fdQhyDNPRJ73qTyHYDX1HbQXL/PV1QY7PtYjYb/++GIFtNcYGUG5
mAlRZlEpnvC82ce4CBamR9B5uQjRhE/iBjSyLBDZCPCPuDdH0bGHXRSRKkM5KRTB
UAAsVZXZ2vXzue407ibTQhQ/e8eOdEugRLK4jzpr7FSU8K0vqGcfkJJnvBw75nnk
FDsTbln78cnNcIAqzs4KG94lU+MHe3io6kJtL691vKbQ+SjFLKiawcubO+URmng8
hCWlVb9OLZOJEEP2I+0VmQocTxMWJaqLSQGxOI2U9B37OGf9JUm7rRpVaHyYUArm
e7B+hPylAgMBAAECggEAC8DQiUXit0kmFzA43BR+2eBmda9NaHvi9tLUUrLSRN5S
SNvOQqkbz1dkvG9HCiRKNiea7n+qSuug86FQbwa4OysH/MEi063rugqHFuVzqgIa
Yii/UQ654VNvzeu1XbHUgVnqr1y8vSiOq0/oknoQU+ZJ8VEBletyP0+A02GPfQR/
RqTrNgUgXzb1dDNdtKIe9IFDhhsRCZ3qi5oJsHlCAYUgod7c4kCqvDX6mAOqgNAF
kWULrdq7Rh2hJf0S4M3AF/46wybXauL+Wn1xK9VChEboM67sJ/F8LKfLuUvqMUnN
46ilayIyCDdRLng1yEFAoUBNQEMsf1V7CGECSSSqwQKBgQD7W2JmPBKU7O4SQmnN
yxMyE5501V9OyRgolPQdeLoLubj3ZlNifrBNC3Dj6vfsumAZwyHwF14JXh0EzJHG
txnLceN2J68FyvbEOw1S98ifOJYHpvsOS5/9r+uyGdcEATQMuLw0whAcp1T3Y0BF
8HKsRI5tpGqGgi37ZL/VMjYnWQKBgQDefTXNpwIQMs7ff1YXAdrKkID/ROyepGXB
u/0G6jWBUKkOTQNcB8cEfRdjgOk2AyiOkV9neFkwtBdWPzJRzPmtjOSF2ajbFazC
2rVuiadh5ZH+aJuu2eEBGivv0R/0kRiXeOsbXKwPLIsn6n20h5zqPka3GvZa9+58
RuwEYXBiLQKBgQCL0z081AQeNmMFY06KPYKzI7jNE3lOUY2P3bSixGryZOFhNtoB
+6nFYiztjONYHCGjkypI7ibQnTsVVVtuqKK/yt4W92JknZCCfrsdwVsoP4kuPpSA
Uk9xBzDdRYSX5Ld4sDd6Pc5KskcQy3SQs36HGCgHFCRyKO69X0Fbru/zGQKBgC6I
i5c+pdzTc5clH9FiDuus+33oYYDwq2OwuMQYeiZYw3L9QoWeDs7uhtTF4oDsejAP
UZ/neOgJ0pO0Vgbr0xCsViN0ma9wwhhi++1plvuPs1A9espAQaIkYiofWAqjyjvs
C2hGoqntzBEGJ1J5xqTrb4jed8Yg8t1FTBnCc2nlAoGABIwyFvJz/Lz/SOEUTFf1
KTQN4AT9T1ywcqp0fFuuFMRAyQFwVArOV/KGu/ZUkG3Cndt4pzzYMTpsMsAhN7id
abWL0QyzeO2vsBKI5FEC4uj5zxeEvHH+QIfKf6qL2cWWXIoEDVsRv6jLI/nCj4Lt
ty2lFdcrOzSIFC36iURd0ps=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1 @@
6292D0A4D7B092E361D6DD22C2FD7831479D48D9

View File

@@ -0,0 +1,18 @@
欢迎使用微信支付!
附件中的三份文件证书pkcs12格式、证书pem格式、证书密钥pem格式,为接口中强制要求时需携带的证书文件。
证书属于敏感信息,请妥善保管不要泄露和被他人复制。
不同开发语言下的证书格式不同,以下为说明指引:
证书pkcs12格式apiclient_cert.p12
包含了私钥信息的证书文件为p12(pfx)格式,由微信支付签发给您用来标识和界定您的身份
部分安全性要求较高的API需要使用该证书来确认您的调用身份
windows上可以直接双击导入系统导入过程中会提示输入证书密码证书密码默认为您的商户号1900006031
证书pem格式apiclient_cert.pem
从apiclient_cert.p12中导出证书部分的文件为pem格式请妥善保管不要泄漏和被他人复制
部分开发语言和环境不能直接使用p12文件而需要使用pem所以为了方便您使用已为您直接提供
您也可以使用openssl命令来自己导出openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem
证书密钥pem格式apiclient_key.pem
从apiclient_cert.p12中导出密钥部分的文件为pem格式
部分开发语言和环境不能直接使用p12文件而需要使用pem所以为了方便您使用已为您直接提供
您也可以使用openssl命令来自己导出openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem
备注说明:
由于绝大部分操作系统已内置了微信支付服务器证书的根CA证书, 2018年3月6日后, 不再提供CA证书文件rootca.pem下载

View File

@@ -10,6 +10,7 @@ python-jose[cryptography]==3.3.0 # 兼容 Python3 的 JOSE 实现
pycryptodome==3.20.0 # 替代旧版 pycrypto 的现代加密库 pycryptodome==3.20.0 # 替代旧版 pycrypto 的现代加密库
requests>=2.25.1,<3.0.0 requests>=2.25.1,<3.0.0
httpx
# MySQL 驱动 # MySQL 驱动
pymysql==1.1.0 pymysql==1.1.0
@@ -18,4 +19,8 @@ pymysql==1.1.0
dbutils==3.1.1 dbutils==3.1.1
tenacity>=8.2.3 # 推荐使用最新稳定版 tenacity>=8.2.3 # 推荐使用最新稳定版
openai>=1.0.0 # 这是OpenAI官方Python SDK
python-dotenv>=0.19.0 # 用于加载.env文件
cryptography>=42.0.0
python-dateutil

View File

@@ -0,0 +1,74 @@
#!/bin/bash
# 1. 终止占用9580端口的进程
PORT=9580
echo "➜ 正在检查端口 ${PORT} 的占用情况..."
PID=$(lsof -ti :${PORT})
if [ -n "$PID" ]; then
echo "➜ 发现占用端口的进程(PID: ${PID}),正在终止..."
kill -9 $PID
sleep 2 # 等待进程完全终止
echo "✓ 已终止进程"
else
echo "✓ 端口 ${PORT} 未被占用"
fi
# 2. 激活虚拟环境并设置PYTHONPATH
source venv/bin/activate
export PYTHONPATH=.:$PYTHONPATH
# 3. 使用nohup运行并防止终端退出影响
echo "➜ 启动应用进程..."
nohup python app/main.py > app.log 2>&1 &
# 4. 验证新进程
sleep 3 # 等待进程启动
NEW_PID=$(lsof -ti :${PORT})
if [ -n "$NEW_PID" ]; then
echo "✓ 应用启动成功PID: ${NEW_PID}"
echo "➜ 日志输出已重定向到 app.log"
echo "➜ 使用以下命令查看日志:"
echo " tail -f app.log"
else
echo "✗ 应用启动失败,请检查日志"
exit 1
fi

View File

@@ -1,2 +1,16 @@
# 1. 终止占用9580端口的进程
PORT=9580
echo "➜ 正在检查端口 ${PORT} 的占用情况..."
PID=$(lsof -ti :${PORT})
if [ -n "$PID" ]; then
echo "➜ 发现占用端口的进程(PID: ${PID}),正在终止..."
kill -9 $PID
sleep 2 # 等待进程完全终止
echo "✓ 已终止进程"
else
echo "✓ 端口 ${PORT} 未被占用"
fi
source venv/bin/activate source venv/bin/activate
export PYTHONPATH=.:$PYTHONPATH && python app/main.py export PYTHONPATH=.:$PYTHONPATH && python app/main.py

View File

@@ -98,6 +98,50 @@ class RAGFlowMinio(object):
time.sleep(1) time.sleep(1)
return return
def list_objects(self, bucket: str, prefix: str = "", recursive: bool = True) -> list[dict]:
"""
列出存储桶中指定前缀的所有对象
:param bucket: 存储桶名称
:param prefix: 对象前缀(目录路径)
:param recursive: 是否递归列出
:return: 对象信息列表 [{"name": str, "size": int, "last_modified": datetime}, ...]
"""
objects = []
for attempt in range(3):
try:
# 确保存储桶存在
if not self.conn.bucket_exists(bucket):
logging.warning(f"存储桶不存在: {bucket}")
return []
# 列出对象
result = self.conn.list_objects(bucket, prefix=prefix, recursive=recursive)
# 收集对象信息
for obj in result:
objects.append({
"name": obj.object_name,
"size": obj.size,
"last_modified": obj.last_modified,
"etag": obj.etag,
"content_type": obj.content_type
})
return objects
except S3Error as e:
if e.code == "NoSuchBucket":
logging.warning(f"存储桶不存在: {bucket}")
return []
logging.exception(f"列出对象时发生S3错误: {e}")
except Exception as e:
logging.exception(f"列出对象失败 (尝试 {attempt + 1}/3): {e}")
# 重连并等待
self.__open__()
time.sleep(1)
return []
MINIO = RAGFlowMinio() MINIO = RAGFlowMinio()