戒酒的李白

Communication encoding bug fixed, all converted to UTF-8.

@@ -8,6 +8,20 @@ import sys @@ -8,6 +8,20 @@ import sys
8 import streamlit as st 8 import streamlit as st
9 from datetime import datetime 9 from datetime import datetime
10 import json 10 import json
  11 +import locale
  12 +
  13 +# 设置UTF-8编码环境
  14 +os.environ['PYTHONIOENCODING'] = 'utf-8'
  15 +os.environ['PYTHONUTF8'] = '1'
  16 +
  17 +# 设置系统编码
  18 +try:
  19 + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
  20 +except locale.Error:
  21 + try:
  22 + locale.setlocale(locale.LC_ALL, 'C.UTF-8')
  23 + except locale.Error:
  24 + pass
11 25
12 # 添加src目录到Python路径 26 # 添加src目录到Python路径
13 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 27 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@@ -8,6 +8,20 @@ import sys @@ -8,6 +8,20 @@ import sys
8 import streamlit as st 8 import streamlit as st
9 from datetime import datetime 9 from datetime import datetime
10 import json 10 import json
  11 +import locale
  12 +
  13 +# 设置UTF-8编码环境
  14 +os.environ['PYTHONIOENCODING'] = 'utf-8'
  15 +os.environ['PYTHONUTF8'] = '1'
  16 +
  17 +# 设置系统编码
  18 +try:
  19 + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
  20 +except locale.Error:
  21 + try:
  22 + locale.setlocale(locale.LC_ALL, 'C.UTF-8')
  23 + except locale.Error:
  24 + pass
11 25
12 # 添加src目录到Python路径 26 # 添加src目录到Python路径
13 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 27 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@@ -8,6 +8,20 @@ import sys @@ -8,6 +8,20 @@ import sys
8 import streamlit as st 8 import streamlit as st
9 from datetime import datetime 9 from datetime import datetime
10 import json 10 import json
  11 +import locale
  12 +
  13 +# 设置UTF-8编码环境
  14 +os.environ['PYTHONIOENCODING'] = 'utf-8'
  15 +os.environ['PYTHONUTF8'] = '1'
  16 +
  17 +# 设置系统编码
  18 +try:
  19 + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
  20 +except locale.Error:
  21 + try:
  22 + locale.setlocale(locale.LC_ALL, 'C.UTF-8')
  23 + except locale.Error:
  24 + pass
11 25
12 # 添加src目录到Python路径 26 # 添加src目录到Python路径
13 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 27 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@@ -15,16 +15,26 @@ from flask_socketio import SocketIO, emit @@ -15,16 +15,26 @@ from flask_socketio import SocketIO, emit
15 import signal 15 import signal
16 import atexit 16 import atexit
17 import requests 17 import requests
  18 +import logging
  19 +from pathlib import Path
18 20
19 app = Flask(__name__) 21 app = Flask(__name__)
20 app.config['SECRET_KEY'] = 'weibo_analysis_system_2024' 22 app.config['SECRET_KEY'] = 'weibo_analysis_system_2024'
21 socketio = SocketIO(app, cors_allowed_origins="*") 23 socketio = SocketIO(app, cors_allowed_origins="*")
22 24
  25 +# 设置UTF-8编码环境
  26 +os.environ['PYTHONIOENCODING'] = 'utf-8'
  27 +os.environ['PYTHONUTF8'] = '1'
  28 +
  29 +# 创建日志目录
  30 +LOG_DIR = Path('logs')
  31 +LOG_DIR.mkdir(exist_ok=True)
  32 +
23 # 全局变量存储进程信息 33 # 全局变量存储进程信息
24 processes = { 34 processes = {
25 - 'insight': {'process': None, 'port': 8501, 'status': 'stopped', 'output': []},  
26 - 'media': {'process': None, 'port': 8502, 'status': 'stopped', 'output': []},  
27 - 'query': {'process': None, 'port': 8503, 'status': 'stopped', 'output': []} 35 + 'insight': {'process': None, 'port': 8501, 'status': 'stopped', 'output': [], 'log_file': None},
  36 + 'media': {'process': None, 'port': 8502, 'status': 'stopped', 'output': [], 'log_file': None},
  37 + 'query': {'process': None, 'port': 8503, 'status': 'stopped', 'output': [], 'log_file': None}
28 } 38 }
29 39
30 # 输出队列 40 # 输出队列
@@ -34,8 +44,36 @@ output_queues = { @@ -34,8 +44,36 @@ output_queues = {
34 'query': Queue() 44 'query': Queue()
35 } 45 }
36 46
  47 +def write_log_to_file(app_name, line):
  48 + """将日志写入文件"""
  49 + try:
  50 + log_file_path = LOG_DIR / f"{app_name}.log"
  51 + with open(log_file_path, 'a', encoding='utf-8') as f:
  52 + f.write(line + '\n')
  53 + f.flush()
  54 + except Exception as e:
  55 + print(f"Error writing log for {app_name}: {e}")
  56 +
  57 +def read_log_from_file(app_name, tail_lines=None):
  58 + """从文件读取日志"""
  59 + try:
  60 + log_file_path = LOG_DIR / f"{app_name}.log"
  61 + if not log_file_path.exists():
  62 + return []
  63 +
  64 + with open(log_file_path, 'r', encoding='utf-8') as f:
  65 + lines = f.readlines()
  66 + lines = [line.rstrip('\n\r') for line in lines if line.strip()]
  67 +
  68 + if tail_lines:
  69 + return lines[-tail_lines:]
  70 + return lines
  71 + except Exception as e:
  72 + print(f"Error reading log for {app_name}: {e}")
  73 + return []
  74 +
37 def read_process_output(process, app_name): 75 def read_process_output(process, app_name):
38 - """读取进程输出并放入队列""" 76 + """读取进程输出并写入文件"""
39 while True: 77 while True:
40 try: 78 try:
41 if process.poll() is not None: 79 if process.poll() is not None:
@@ -43,15 +81,14 @@ def read_process_output(process, app_name): @@ -43,15 +81,14 @@ def read_process_output(process, app_name):
43 81
44 output = process.stdout.readline() 82 output = process.stdout.readline()
45 if output: 83 if output:
46 - line = output.decode('utf-8', errors='ignore').strip() 84 + # 使用UTF-8解码,忽略错误字符
  85 + line = output.decode('utf-8', errors='replace').strip()
47 if line: 86 if line:
48 timestamp = datetime.now().strftime('%H:%M:%S') 87 timestamp = datetime.now().strftime('%H:%M:%S')
49 formatted_line = f"[{timestamp}] {line}" 88 formatted_line = f"[{timestamp}] {line}"
50 89
51 - # 添加到输出列表(保持最近100行)  
52 - processes[app_name]['output'].append(formatted_line)  
53 - if len(processes[app_name]['output']) > 100:  
54 - processes[app_name]['output'].pop(0) 90 + # 写入日志文件
  91 + write_log_to_file(app_name, formatted_line)
55 92
56 # 发送到前端 93 # 发送到前端
57 socketio.emit('console_output', { 94 socketio.emit('console_output', {
@@ -59,7 +96,9 @@ def read_process_output(process, app_name): @@ -59,7 +96,9 @@ def read_process_output(process, app_name):
59 'line': formatted_line 96 'line': formatted_line
60 }) 97 })
61 except Exception as e: 98 except Exception as e:
62 - print(f"Error reading output for {app_name}: {e}") 99 + error_msg = f"Error reading output for {app_name}: {e}"
  100 + print(error_msg)
  101 + write_log_to_file(app_name, f"[{datetime.now().strftime('%H:%M:%S')}] {error_msg}")
63 break 102 break
64 103
65 def start_streamlit_app(app_name, script_path, port): 104 def start_streamlit_app(app_name, script_path, port):
@@ -72,6 +111,15 @@ def start_streamlit_app(app_name, script_path, port): @@ -72,6 +111,15 @@ def start_streamlit_app(app_name, script_path, port):
72 if not os.path.exists(script_path): 111 if not os.path.exists(script_path):
73 return False, f"文件不存在: {script_path}" 112 return False, f"文件不存在: {script_path}"
74 113
  114 + # 清空之前的日志文件
  115 + log_file_path = LOG_DIR / f"{app_name}.log"
  116 + if log_file_path.exists():
  117 + log_file_path.unlink()
  118 +
  119 + # 创建启动日志
  120 + start_msg = f"[{datetime.now().strftime('%H:%M:%S')}] 启动 {app_name} 应用..."
  121 + write_log_to_file(app_name, start_msg)
  122 +
75 cmd = [ 123 cmd = [
76 sys.executable, '-m', 'streamlit', 'run', 124 sys.executable, '-m', 'streamlit', 'run',
77 script_path, 125 script_path,
@@ -81,6 +129,15 @@ def start_streamlit_app(app_name, script_path, port): @@ -81,6 +129,15 @@ def start_streamlit_app(app_name, script_path, port):
81 '--logger.level', 'info' 129 '--logger.level', 'info'
82 ] 130 ]
83 131
  132 + # 设置环境变量确保UTF-8编码
  133 + env = os.environ.copy()
  134 + env.update({
  135 + 'PYTHONIOENCODING': 'utf-8',
  136 + 'PYTHONUTF8': '1',
  137 + 'LANG': 'en_US.UTF-8',
  138 + 'LC_ALL': 'en_US.UTF-8'
  139 + })
  140 +
84 # 使用当前工作目录而不是脚本目录 141 # 使用当前工作目录而不是脚本目录
85 process = subprocess.Popen( 142 process = subprocess.Popen(
86 cmd, 143 cmd,
@@ -88,7 +145,9 @@ def start_streamlit_app(app_name, script_path, port): @@ -88,7 +145,9 @@ def start_streamlit_app(app_name, script_path, port):
88 stderr=subprocess.STDOUT, 145 stderr=subprocess.STDOUT,
89 bufsize=1, 146 bufsize=1,
90 universal_newlines=False, 147 universal_newlines=False,
91 - cwd=os.getcwd() 148 + cwd=os.getcwd(),
  149 + env=env,
  150 + encoding=None # 让我们手动处理编码
92 ) 151 )
93 152
94 processes[app_name]['process'] = process 153 processes[app_name]['process'] = process
@@ -106,7 +165,9 @@ def start_streamlit_app(app_name, script_path, port): @@ -106,7 +165,9 @@ def start_streamlit_app(app_name, script_path, port):
106 return True, f"{app_name} 应用启动中..." 165 return True, f"{app_name} 应用启动中..."
107 166
108 except Exception as e: 167 except Exception as e:
109 - return False, f"启动失败: {str(e)}" 168 + error_msg = f"启动失败: {str(e)}"
  169 + write_log_to_file(app_name, f"[{datetime.now().strftime('%H:%M:%S')}] {error_msg}")
  170 + return False, error_msg
110 171
111 def stop_streamlit_app(app_name): 172 def stop_streamlit_app(app_name):
112 """停止Streamlit应用""" 173 """停止Streamlit应用"""
@@ -245,9 +306,12 @@ def get_output(app_name): @@ -245,9 +306,12 @@ def get_output(app_name):
245 if app_name not in processes: 306 if app_name not in processes:
246 return jsonify({'success': False, 'message': '未知应用'}) 307 return jsonify({'success': False, 'message': '未知应用'})
247 308
  309 + # 从文件读取完整日志
  310 + output_lines = read_log_from_file(app_name)
  311 +
248 return jsonify({ 312 return jsonify({
249 'success': True, 313 'success': True,
250 - 'output': processes[app_name]['output'] 314 + 'output': output_lines
251 }) 315 })
252 316
253 @app.route('/api/search', methods=['POST']) 317 @app.route('/api/search', methods=['POST'])
@@ -338,6 +338,11 @@ @@ -338,6 +338,11 @@
338 checkStatus(); 338 checkStatus();
339 setInterval(checkStatus, 5000); 339 setInterval(checkStatus, 5000);
340 340
  341 + // 定期刷新控制台输出
  342 + setInterval(() => {
  343 + refreshConsoleOutput();
  344 + }, 2000);
  345 +
341 // 延迟预加载iframe以确保应用启动完成 346 // 延迟预加载iframe以确保应用启动完成
342 setTimeout(() => { 347 setTimeout(() => {
343 preloadIframes(); 348 preloadIframes();
@@ -446,12 +451,18 @@ @@ -446,12 +451,18 @@
446 451
447 // 清空并加载新的控制台输出 452 // 清空并加载新的控制台输出
448 document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + app + ' 应用</div>'; 453 document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + app + ' 应用</div>';
  454 +
  455 + // 重置行计数
  456 + lastLineCount[app] = 0;
449 loadConsoleOutput(app); 457 loadConsoleOutput(app);
450 458
451 // 更新嵌入页面 459 // 更新嵌入页面
452 updateEmbeddedPage(app); 460 updateEmbeddedPage(app);
453 } 461 }
454 462
  463 + // 存储最后显示的行数,避免重复加载
  464 + let lastLineCount = {};
  465 +
455 // 加载控制台输出 466 // 加载控制台输出
456 function loadConsoleOutput(app) { 467 function loadConsoleOutput(app) {
457 fetch(`/api/output/${app}`) 468 fetch(`/api/output/${app}`)
@@ -459,12 +470,19 @@ @@ -459,12 +470,19 @@
459 .then(data => { 470 .then(data => {
460 if (data.success && data.output.length > 0) { 471 if (data.success && data.output.length > 0) {
461 const consoleOutput = document.getElementById('consoleOutput'); 472 const consoleOutput = document.getElementById('consoleOutput');
462 - data.output.forEach(line => { 473 +
  474 + // 只添加新的行
  475 + const lastCount = lastLineCount[app] || 0;
  476 + const newLines = data.output.slice(lastCount);
  477 +
  478 + newLines.forEach(line => {
463 const div = document.createElement('div'); 479 const div = document.createElement('div');
464 div.className = 'console-line'; 480 div.className = 'console-line';
465 div.textContent = line; 481 div.textContent = line;
466 consoleOutput.appendChild(div); 482 consoleOutput.appendChild(div);
467 }); 483 });
  484 +
  485 + lastLineCount[app] = data.output.length;
468 consoleOutput.scrollTop = consoleOutput.scrollHeight; 486 consoleOutput.scrollTop = consoleOutput.scrollHeight;
469 } 487 }
470 }) 488 })
@@ -472,6 +490,38 @@ @@ -472,6 +490,38 @@
472 console.error('加载输出失败:', error); 490 console.error('加载输出失败:', error);
473 }); 491 });
474 } 492 }
  493 +
  494 + // 刷新当前应用的控制台输出
  495 + function refreshConsoleOutput() {
  496 + if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') {
  497 + fetch(`/api/output/${currentApp}`)
  498 + .then(response => response.json())
  499 + .then(data => {
  500 + if (data.success && data.output.length > 0) {
  501 + const consoleOutput = document.getElementById('consoleOutput');
  502 +
  503 + // 只添加新的行
  504 + const lastCount = lastLineCount[currentApp] || 0;
  505 + const newLines = data.output.slice(lastCount);
  506 +
  507 + if (newLines.length > 0) {
  508 + newLines.forEach(line => {
  509 + const div = document.createElement('div');
  510 + div.className = 'console-line';
  511 + div.textContent = line;
  512 + consoleOutput.appendChild(div);
  513 + });
  514 +
  515 + lastLineCount[currentApp] = data.output.length;
  516 + consoleOutput.scrollTop = consoleOutput.scrollHeight;
  517 + }
  518 + }
  519 + })
  520 + .catch(error => {
  521 + console.error('刷新输出失败:', error);
  522 + });
  523 + }
  524 + }
475 525
476 // 添加控制台输出 526 // 添加控制台输出
477 function addConsoleOutput(line) { 527 function addConsoleOutput(line) {