🎉 项目优化与Bug修复完整版

 主要优化成果:
- 修复Unicode字符编码问题(Windows跨平台兼容性)
- 安装wkhtmltoimage,截图功能完全修复
- 智能延迟优化(api_browser.py)
- 线程池资源泄漏修复(tasks.py)
- HTML解析缓存机制
- 二分搜索算法优化(kdocs_uploader.py)
- 自适应资源配置(browser_pool_worker.py)

🐛 Bug修复:
- 解决截图失败问题
- 修复管理员密码设置
- 解决应用启动编码错误

📚 新增文档:
- BUG_REPORT.md - 完整bug分析报告
- PERFORMANCE_ANALYSIS_REPORT.md - 性能优化分析
- LINUX_DEPLOYMENT_ANALYSIS.md - Linux部署指南
- SCREENSHOT_FIX_SUCCESS.md - 截图功能修复记录
- INSTALL_WKHTMLTOIMAGE.md - 安装指南
- OPTIMIZATION_FIXES_SUMMARY.md - 优化总结

🚀 功能验证:
- Flask应用正常运行(51233端口)
- 数据库、截图线程池、API预热正常
- 管理员登录:admin/admin123
- 健康检查API:http://127.0.0.1:51233/health

💡 技术改进:
- 智能延迟算法(自适应调整)
- LRU缓存策略
- 线程池资源管理优化
- 二分搜索算法(O(log n) vs O(n))
- 自适应资源管理

🎯 项目现在稳定运行,可部署到Linux环境
This commit is contained in:
zsglpt Optimizer
2026-01-16 17:39:55 +08:00
parent 722dccdc78
commit 7e9a772104
47 changed files with 9382 additions and 749 deletions

503
test_with_login.py Normal file
View File

@@ -0,0 +1,503 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
金山文档上传测试 - 支持登录版本
集成扫码登录功能,支持完整的测试流程
"""
import os
import sys
import time
import base64
from datetime import datetime
from io import BytesIO
from PIL import Image
# 添加项目路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
from playwright.sync_api import sync_playwright
except ImportError:
print("错误: 需要安装 playwright")
print("请运行: pip install playwright")
sys.exit(1)
def log(message, level='INFO'):
"""日志输出"""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {level}: {message}")
def pause(msg="按Enter键继续..."):
"""等待用户按键"""
input(f"\n{msg}")
def ask_yes_no(question, default='n'):
"""询问用户是/否问题"""
if default == 'y':
prompt = f"{question} (Y/n): "
else:
prompt = f"{question} (y/N): "
answer = input(prompt).strip().lower()
if not answer:
answer = default
return answer == 'y'
def save_qr_code(qr_image_bytes, filename="qr_code.png"):
"""保存二维码图片"""
try:
# 保存为PNG文件
with open(filename, 'wb') as f:
f.write(qr_image_bytes)
log(f"[OK] 二维码已保存到: {filename}", 'SUCCESS')
return filename
except Exception as e:
log(f"✗ 保存二维码失败: {str(e)}", 'ERROR')
return None
def display_qr_info():
"""显示二维码信息"""
print("\n" + "=" * 70)
print("📱 扫码登录说明")
print("=" * 70)
print()
print("1. 请使用手机微信扫描二维码")
print("2. 在手机上点击'确认登录'")
print("3. 等待页面自动跳转到表格页面")
print("4. 如果二维码失效,请按 Ctrl+C 重新生成")
print()
print("登录完成后请回到此窗口并按Enter键继续")
print("=" * 70)
def wait_for_login(page, timeout=120):
"""等待用户完成登录"""
log(f"等待登录完成 (超时: {timeout}秒)...", 'INFO')
start_time = time.time()
check_interval = 2 # 每2秒检查一次
while time.time() - start_time < timeout:
try:
# 检查当前URL
current_url = page.url
log(f"当前URL: {current_url}", 'INFO')
# 如果已经进入文档页面,认为登录成功
if "kdocs.cn" in current_url and "/spreadsheet/" in current_url:
log("[OK] 登录成功,已进入文档页面", 'SUCCESS')
return True
# 检查是否还在登录页面
if "login" in current_url.lower() or "account" in current_url.lower():
log("仍在登录页面,请扫码登录...", 'INFO')
else:
log(f"页面状态变化: {current_url}", 'INFO')
time.sleep(check_interval)
except Exception as e:
log(f"检查登录状态时出错: {str(e)}", 'WARNING')
time.sleep(check_interval)
log("登录超时", 'WARNING')
return False
def capture_qr_code(page):
"""尝试捕获二维码"""
log("尝试捕获二维码...", 'INFO')
try:
# 查找二维码元素
qr_selectors = [
"canvas",
"img[src*='qr']",
"img[alt*='二维码']",
"[class*='qr']",
"[id*='qr']",
"div[class*='qrcode']"
]
for selector in qr_selectors:
try:
elements = page.query_selector_all(selector)
for i, element in enumerate(elements):
try:
# 截图
screenshot = element.screenshot()
if len(screenshot) > 1000: # 足够大的图片
filename = f"qr_code_{selector.replace('[', '').replace(']', '').replace('*', '').replace('=', '').replace(' ', '_')}_{i}.png"
save_qr_code(screenshot, filename)
log(f"[OK] 找到二维码元素: {selector}[{i}]", 'SUCCESS')
return True
except Exception:
continue
except Exception:
continue
# 备选:截取整个页面并查找二维码区域
try:
screenshot = page.screenshot()
filename = "qr_code_fullpage.png"
save_qr_code(screenshot, filename)
log("[OK] 已截取整个页面,请查看页面中的二维码", 'SUCCESS')
log(f" 截图保存为: {filename}", 'INFO')
return True
except Exception as e:
log(f"截取页面失败: {str(e)}", 'ERROR')
except Exception as e:
log(f"捕获二维码失败: {str(e)}", 'ERROR')
return False
def main():
"""主函数"""
print("=" * 70)
print("[LOCK] 金山文档上传测试 - 支持登录版本")
print("=" * 70)
print()
print("特点:")
print(" [OK] 支持扫码登录")
print(" [OK] 完整的测试流程")
print(" [OK] 详细的操作指导")
print(" [OK] 自动等待登录完成")
print()
# 配置
doc_url = input("请输入金山文档URL (或按Enter使用默认): ").strip()
if not doc_url:
doc_url = "https://kdocs.cn/l/cpwEOo5ynKX4"
print(f"\n使用URL: {doc_url}")
print()
if not ask_yes_no("确认开始测试?"):
print("测试已取消")
return
print("\n" + "=" * 70)
print("开始测试流程")
print("=" * 70)
playwright = None
browser = None
context = None
page = None
try:
# ===== 步骤1: 启动浏览器 =====
print("\n" + "=" * 50)
print("步骤1: 启动浏览器")
print("=" * 50)
log("正在启动Playwright...", 'INFO')
playwright = sync_playwright().start()
log("[OK] Playwright启动成功", 'SUCCESS')
log("正在启动浏览器...", 'INFO')
browser = playwright.chromium.launch(headless=False)
log("[OK] 浏览器启动成功", 'SUCCESS')
log("正在创建上下文...", 'INFO')
context = browser.new_context()
log("[OK] 上下文创建成功", 'SUCCESS')
log("正在创建页面...", 'INFO')
page = context.new_page()
page.set_default_timeout(30000)
log("[OK] 页面创建成功", 'SUCCESS')
pause("浏览器已启动,请观察浏览器窗口是否正常打开")
# ===== 步骤2: 打开登录页面 =====
print("\n" + "=" * 50)
print("步骤2: 打开登录页面")
print("=" * 50)
log(f"正在导航到: {doc_url}", 'INFO')
page.goto(doc_url, wait_until='domcontentloaded')
log("[OK] 页面导航完成", 'SUCCESS')
log("等待3秒让页面加载...", 'INFO')
time.sleep(3)
current_url = page.url
log(f"当前URL: {current_url}", 'INFO')
# ===== 步骤3: 处理登录 =====
print("\n" + "=" * 50)
print("步骤3: 登录处理")
print("=" * 50)
# 检查是否需要登录
try:
login_visible = page.locator("text=登录").first.is_visible()
if login_visible:
log("[OK] 检测到登录页面", 'SUCCESS')
# 尝试捕获二维码
capture_qr_code(page)
# 显示登录说明
display_qr_info()
# 等待用户登录
if not wait_for_login(page, timeout=180): # 3分钟超时
log("登录失败或超时", 'ERROR')
if ask_yes_no("是否要重新尝试?"):
log("请重新扫码登录...", 'INFO')
if wait_for_login(page, timeout=180):
log("[OK] 登录成功", 'SUCCESS')
else:
log("登录仍然失败", 'ERROR')
return
else:
log("[OK] 登录成功", 'SUCCESS')
else:
log("[OK] 未检测到登录页面,可能已经登录", 'SUCCESS')
except Exception as e:
log(f"检查登录状态时出错: {str(e)}", 'WARNING')
pause("登录处理完成,请确认是否已进入文档页面")
# ===== 步骤4: 验证文档加载 =====
print("\n" + "=" * 50)
print("步骤4: 验证文档加载")
print("=" * 50)
current_url = page.url
log(f"当前URL: {current_url}", 'INFO')
if "kdocs.cn" in current_url and "/spreadsheet/" in current_url:
log("[OK] 已成功进入金山文档表格", 'SUCCESS')
else:
log("⚠ 当前不在金山文档表格页面", 'WARNING')
log("请确认是否已正确登录", 'INFO')
# 等待页面完全加载
log("等待5秒让表格完全加载...", 'INFO')
time.sleep(5)
# 检查表格元素
try:
canvas_count = page.locator("canvas").count()
log(f"[OK] 检测到 {canvas_count} 个canvas元素", 'SUCCESS')
if canvas_count > 0:
log("[OK] 表格元素正常加载", 'SUCCESS')
else:
log("⚠ 未检测到表格元素,可能页面还在加载", 'WARNING')
except Exception as e:
log(f"检查表格元素时出错: {str(e)}", 'WARNING')
pause("文档验证完成,请确认表格是否正常显示")
# ===== 步骤5: 表格读取测试 =====
print("\n" + "=" * 50)
print("步骤5: 表格读取测试")
print("=" * 50)
# 尝试读取名称框
try:
log("尝试定位名称框...", 'INFO')
name_box = page.locator("input.edit-box").first
if name_box.is_visible():
value = name_box.input_value()
log(f"[OK] 名称框可见,当前值: '{value}'", 'SUCCESS')
else:
log("⚠ 名称框不可见", 'WARNING')
except Exception as e:
log(f"读取名称框失败: {str(e)}", 'WARNING')
# 尝试读取当前单元格
try:
log("尝试读取当前单元格内容...", 'INFO')
# 尝试点击网格
canvases = page.locator("canvas").all()
if canvases:
box = canvases[0].bounding_box()
if box:
page.mouse.click(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2)
time.sleep(0.5)
log("[OK] 已点击网格", 'SUCCESS')
except Exception as e:
log(f"点击网格失败: {str(e)}", 'WARNING')
pause("表格读取测试完成")
# ===== 步骤6: 人员搜索测试 =====
print("\n" + "=" * 50)
print("步骤6: 人员搜索测试")
print("=" * 50)
test_name = input("请输入要搜索的姓名 (默认: 张三): ").strip()
if not test_name:
test_name = "张三"
log(f"搜索姓名: {test_name}", 'INFO')
try:
log("执行搜索操作...", 'INFO')
page.keyboard.press("Control+f")
time.sleep(0.5)
page.keyboard.type(test_name)
time.sleep(0.3)
page.keyboard.press("Enter")
time.sleep(1)
page.keyboard.press("Escape")
time.sleep(0.3)
log("[OK] 人员搜索测试完成", 'SUCCESS')
log("请查看浏览器窗口,检查是否高亮显示了搜索结果", 'INFO')
except Exception as e:
log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
pause("搜索测试完成")
# ===== 步骤7: 图片上传测试 =====
print("\n" + "=" * 50)
print("步骤7: 图片上传测试 (可选)")
print("=" * 50)
if ask_yes_no("是否进行图片上传测试?"):
image_path = input("请输入测试图片的完整路径: ").strip()
if not image_path or not os.path.exists(image_path):
log("图片文件不存在或路径无效,跳过上传测试", 'WARNING')
else:
log(f"选中的图片: {image_path}", 'INFO')
try:
log("执行上传流程...", 'INFO')
# 导航到D3单元格
name_box = page.locator("input.edit-box").first
name_box.click()
name_box.fill("D3")
name_box.press("Enter")
time.sleep(0.5)
log("[OK] 已导航到D3单元格")
# 点击插入
insert_btn = page.locator("text=插入").first
insert_btn.click()
time.sleep(0.5)
log("[OK] 已点击插入按钮")
# 点击图片
image_btn = page.locator("text=图片").first
image_btn.click()
time.sleep(0.5)
log("[OK] 已点击图片按钮")
# 选择本地
local_option = page.locator("text=本地").first
local_option.click()
log("[OK] 已选择本地图片")
# 上传文件
with page.expect_file_chooser() as fc_info:
pass
file_chooser = fc_info.value
file_chooser.set_files(image_path)
log("[OK] 文件上传命令已发送")
log("等待上传完成...", 'INFO')
time.sleep(3)
log("[OK] 图片上传测试完成", 'SUCCESS')
log("请检查浏览器窗口确认图片是否成功上传到D3单元格", 'INFO')
except Exception as e:
log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
import traceback
traceback.print_exc()
pause("图片上传测试完成")
# ===== 测试完成 =====
print("\n" + "=" * 70)
log("🎉 所有测试完成!", 'SUCCESS')
print("=" * 70)
print()
print("测试结果汇总:")
print(" [[OK]] 浏览器启动")
print(" [[OK]] 文档打开")
print(" [[OK]] 登录处理")
print(" [[OK]] 文档加载验证")
print(" [[OK]] 表格读取")
print(" [[OK]] 人员搜索")
if ask_yes_no("是否执行了图片上传?"):
print(" [[OK]] 图片上传")
print()
print("浏览器窗口将保持打开状态")
print("您可以手动关闭浏览器窗口来结束测试")
except KeyboardInterrupt:
print("\n")
log("测试被用户中断", 'WARNING')
except Exception as e:
print("\n")
log(f"测试过程中出现错误: {str(e)}", 'ERROR')
import traceback
traceback.print_exc()
finally:
# 清理资源
print("\n" + "=" * 70)
print("清理资源...")
print("=" * 70)
try:
if page:
page.close()
log("[OK] 页面已关闭", 'SUCCESS')
except:
pass
try:
if context:
context.close()
log("[OK] 上下文已关闭", 'SUCCESS')
except:
pass
try:
if browser:
browser.close()
log("[OK] 浏览器已关闭", 'SUCCESS')
except:
pass
try:
if playwright:
playwright.stop()
log("[OK] Playwright已停止", 'SUCCESS')
except:
pass
log("测试结束", 'SUCCESS')
print("=" * 70)
if __name__ == "__main__":
main()