Merge pull request '0.3' (#58) from main into huangjiunyuna

huangjiunyuna
p9o3yklam 4 months ago
commit 40bdcf3d8f

6
.gitignore vendored

@ -199,6 +199,7 @@ temp/
# Project specific
dist_package/
dist_package_v0.3/
*.zip
*.pyc
*.pyo
@ -211,6 +212,11 @@ dist_package/
/resources/user_data/
/resources/cache/
# Resources - keep icons and config
!/resources/config/
!/resources/config/icons/
!/resources/icons/
# Test reports
htmlcov/
.coverage

@ -87,4 +87,68 @@
- 实现多重IP定位备份机制
- 添加智能城市名解析和映射
- 优化API调用性能和错误恢复
- 增强代码模块化和可维护性
- 增强代码的模块化和可维护性
## [0.2.1] - 2025-10-20
### 新增
- 集成每日一言功能到WordRibbon界面
- 添加每日一言自动获取和显示功能
- 实现每日一言刷新按钮和手动刷新功能
- 添加每日一言显示/隐藏切换功能
- 集成天气功能到WordRibbon工具栏
- 实现天气信息状态栏显示
- 添加城市选择和天气刷新功能
### 更改
- 将视图菜单中的"天气信息"选项重命名为"附加工具"
- 优化每日一言显示格式,移除"每日一言:"前缀
- 改进天气信息状态栏显示文本
- 统一UI界面风格和交互逻辑
- 优化错误处理和用户反馈信息
### 修复
- 修复每日一言API集成问题
- 修复天气数据解析和显示错误
- 修复UI组件显示/隐藏状态同步问题
- 修复网络请求异常处理
### 技术改进
- 重构WordRibbon类结构增强可扩展性
- 优化API调用和数据处理逻辑
- 改进组件间的通信机制
- 增强代码的模块化和可维护性
### 发布/构建与工程维护 - 2025-10-22
- 新增Apple ARM64的软件
### 未来计划-2025-10-23
- 新增断点记录
- 改进页面更像word
- 新增切换输入模式功能
<<<<<<< HEAD
- 详细天气模块中,去除天气预报,只显示当前天气
=======
- 详细天气模块中,去除天气预报,只显示当前天气
## [0.2.2] - 2025-10-25
### 修改
- 更改应用程序图标:现在使用类似 Microsoft Word 的图标,但将字母 "W" 更改为 "M" 以代表 MagicWord
- 图标文件位于 `resources/icons/app_icon.png`
- 支持多种分辨率的图标32x32, 64x64, 128x128, 256x256
## [0.2.3] - 2025-10-26
### 新增
- 全新的 Word 风格用户界面
- 功能区Ribbon设计
- 改进的文档处理功能
- 天气显示功能
- 每日一句名言功能
### 修改
- 重构了整个用户界面以模仿 Microsoft Word
- 改进了打字伪装功能
- 增强了文件处理能力
>>>>>>> shixinglin

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord 0.2.0 版本发布脚本
MagicWord 0.3.0 版本发布脚本
用于构建和打包应用程序
"""
@ -72,7 +72,7 @@ def build_executable():
pyinstaller_cmd = [
"pyinstaller",
"--name", "MagicWord",
"--version", "0.2.0",
"--version", "0.3.0",
"--distpath", "dist",
"--workpath", "build",
"--specpath", ".",
@ -89,7 +89,8 @@ def build_executable():
"--hidden-import", "ebooklib",
"--hidden-import", "chardet",
"--hidden-import", "PIL",
"--icon", "resources/icons/app_icon.ico",
# 移除有问题的图标参数
# "--icon", "resources/icons/app_icon.ico",
"--windowed", # 无控制台窗口
"--noconfirm",
"src/main.py"
@ -160,7 +161,7 @@ def create_package():
# 创建运行脚本
if platform.system() == "Windows":
run_script = """@echo off
echo MagicWord 0.2.0 启动中...
echo MagicWord 0.3.0 启动中...
cd /d "%~dp0"
start MagicWord.exe
"""
@ -168,7 +169,7 @@ start MagicWord.exe
f.write(run_script)
else:
run_script = """#!/bin/bash
echo "MagicWord 0.2.0 启动中..."
echo "MagicWord 0.3.0 启动中..."
cd "$(dirname "$0")"
./MagicWord &
"""
@ -177,7 +178,7 @@ cd "$(dirname "$0")"
os.chmod(os.path.join(release_dir, "run.sh"), 0o755)
# 创建发布说明
release_info = f"""MagicWord 0.2.0 发布包
release_info = f"""MagicWord 0.3.0 发布包
构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
平台: {platform.system()} {platform.machine()}
Python版本: {platform.python_version()}
@ -192,6 +193,8 @@ Python版本: {platform.python_version()}
- 自动IP定位功能
- 40+个城市支持
- 中英文城市名智能映射
- 每日一言功能集成
- 附加工具菜单原天气信息菜单重命名
详细更新请查看 CHANGELOG.md
"""
@ -200,7 +203,7 @@ Python版本: {platform.python_version()}
f.write(release_info)
# 创建ZIP包
zip_name = f"MagicWord_v0.2.0_{platform.system()}_{platform.machine()}.zip"
zip_name = f"MagicWord_v0.3.0_{platform.system()}_{platform.machine()}.zip"
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(release_dir):
for file in files:
@ -214,7 +217,7 @@ Python版本: {platform.python_version()}
def main():
"""主函数"""
print("=" * 60)
print("MagicWord 0.2.0 版本发布构建脚本")
print("MagicWord 0.3.0 版本发布构建脚本")
print("=" * 60)
# 检查Python版本

@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""
MagicWord v0.3 打包脚本
版本号0.3
输出目录dist/
图标resources/icons/app_icon_256X256.png
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path
def clean_dist():
"""清理dist目录"""
dist_dir = Path("dist")
if dist_dir.exists():
shutil.rmtree(dist_dir)
dist_dir.mkdir(exist_ok=True)
def build_executable():
"""使用PyInstaller构建可执行文件"""
print("开始构建 MagicWord v0.3...")
# 清理之前的构建
clean_dist()
# PyInstaller 参数
pyinstaller_args = [
"-m", "PyInstaller",
"--onefile", # 单文件模式
"--windowed", # 窗口模式(无控制台)
f"--name=MagicWord_v0.3", # 可执行文件名
f"--icon=resources/icons/app_icon_256X256.png", # 图标文件
"--add-data=resources:resources", # 添加资源文件
"--add-data=src:src", # 添加源代码
"--clean", # 清理临时文件
"--noconfirm", # 不确认覆盖
"src/main.py" # 主程序入口
]
# 执行打包命令
cmd = [sys.executable] + pyinstaller_args
print(f"执行命令: {' '.join(cmd)}")
try:
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"打包失败: {result.stderr}")
return False
else:
print("打包成功!")
return True
except Exception as e:
print(f"执行打包时出错: {e}")
return False
def check_output():
"""检查输出文件"""
dist_dir = Path("dist")
exe_files = list(dist_dir.glob("MagicWord_v0.3*"))
if exe_files:
print(f"生成的文件:")
for exe in exe_files:
size = exe.stat().st_size / (1024 * 1024) # MB
print(f" {exe.name} - {size:.1f} MB")
return True
else:
print("未找到生成的可执行文件")
return False
def main():
"""主函数"""
print("=" * 50)
print("MagicWord v0.3 打包工具")
print("=" * 50)
# 检查Python环境
print(f"Python版本: {sys.version}")
print(f"Python路径: {sys.executable}")
# 检查文件是否存在
required_files = [
"src/main.py",
"resources/icons/app_icon_256X256.png"
]
missing_files = []
for file in required_files:
if not Path(file).exists():
missing_files.append(file)
if missing_files:
print(f"缺少必需文件: {missing_files}")
return
# 构建可执行文件
if build_executable():
# 检查输出
if check_output():
print("\n✅ 打包完成!可执行文件位于 dist/ 目录")
else:
print("\n❌ 打包可能存在问题")
else:
print("\n❌ 打包失败")
if __name__ == "__main__":
main()

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord 0.2.0 手动发布包创建脚本
MagicWord 0.2.1 手动发布包创建脚本
"""
import os
@ -11,7 +11,7 @@ from datetime import datetime
def create_manual_package():
"""创建手动发布包"""
print("创建 MagicWord 0.2.0 手动发布包...")
print("创建 MagicWord 0.2.1 手动发布包...")
# 创建发布目录
release_dir = "dist_package"
@ -53,14 +53,14 @@ def create_manual_package():
# 创建运行脚本
run_bat = """@echo off
echo MagicWord 0.2.0 启动中...
echo MagicWord 0.2.1 启动中...
cd /d "%~dp0"
python src/main.py
pause
"""
run_sh = """#!/bin/bash
echo "MagicWord 0.2.0 启动中..."
echo "MagicWord 0.2.1 启动中..."
cd "$(dirname "$0")"
python3 src/main.py
"""
@ -73,7 +73,7 @@ python3 src/main.py
# 创建安装脚本
install_bat = """@echo off
echo 正在安装 MagicWord 0.2.0 依赖...
echo 正在安装 MagicWord 0.2.1 依赖...
python -m pip install -r requirements.txt
echo 安装完成!
pause
@ -83,7 +83,7 @@ pause
f.write(install_bat)
# 创建发布说明
release_info = f"""MagicWord 0.2.0 手动发布包
release_info = f"""MagicWord 0.2.1 手动发布包
构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
安装和运行说明:
@ -102,6 +102,8 @@ pause
- 40+个城市支持
- 中英文城市名智能映射
- Microsoft Word风格界面
- 每日一言功能集成
- 附加工具菜单原天气信息菜单重命名
详细更新请查看 CHANGELOG.md
@ -112,7 +114,7 @@ pause
f.write(release_info)
# 创建ZIP包
zip_name = f"MagicWord_v0.2.0_Manual_Package.zip"
zip_name = f"MagicWord_v0.2.1_Manual_Package.zip"
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(release_dir):
for file in files:
@ -166,7 +168,7 @@ def verify_package():
def main():
"""主函数"""
print("=" * 60)
print("MagicWord 0.2.0 手动发布包创建工具")
print("MagicWord 0.2.1 手动发布包创建工具")
print("=" * 60)
# 创建发布包

@ -0,0 +1,287 @@
《软件工程课程设计》
软件设计规格说明书
MagicWord
组长学号姓名: 230340211 马子昂
成员学号姓名: 230340233 石兴霖
230340219 黄俊源
230340210 陆冲
230340235 马明义
二〇二五年九月
第1章 引言
1.1 软件设计目标和原则
设计目标
隐私学习体验允许用户在看似普通文档编辑的环境中学习内容如试卷、单词或古文从而避免他人的闲言碎语实现“在Word里打开试卷但他人看你是在敲文档”的效果。
多格式文件支持能够打开和处理多种文件格式包括doc、txt、pdf和epub以确保用户能够导入不同类型的学习材料。
学习功能集成:支持单词学习、古文背诵等具体学习场景,通过打字输入输出相应内容,使学习过程交互化和可控。
用户界面仿Word设计提供与Microsoft Word相似的页面布局和体验减少用户的学习曲线增强熟悉感和易用性。
附加功能增强:集成额外功能如查看天气、每日一言、历史上的今天和黄历等,以提升软件的实用性和用户粘性。
图片输出支持:能够处理并输出文件中的图片,确保图片在打字输入过程中正常显示,并控制图片大小。
设计原则
用户中心原则软件设计以用户需求为核心注重隐私保护和用户体验例如通过模仿Word界面来降低使用门槛。
兼容性与扩展性原则支持多种文件格式特别是pdf和epub表明设计时考虑了兼容性和未来可能的功能扩展尽管这带来了技术挑战。
功能丰富性原则:不仅专注于核心学习功能,还集成天气、每日一言等附加元素,以提供更全面的服务,增加软件的价值。
创新性原则:瞄准市场上未有的功能(“开荒”),致力于解决独特痛点,如通过打字输入控制内容输出,体现创新思维。
可行性驱动原则在设计中承认潜在风险如处理pdf文件的困难、图片输出的技术问题强调务实 approach确保开发过程注重技术可行性和问题解决。
1.2 软件设计的约束和限制
- 运行环境要求Windows/MacOS
- 开发语言python
- 标准规范python编码风格规范
- 开发工具PyCharm
第2章 软件体系结构设计
图 2-1 描述了 “MagicWord” 系统的设计架构,包括了用户界面层、业务逻辑层及数据层。其中:
界面层负责显示用户界面,承担了边界类 “伪装界面模块类”“输入处理模块类”“信息展示模块类” 与用户交互有关的职责。
业务逻辑层负责核心业务逻辑处理,它将承担 “进度管理模块类”“伪装学习控制器类”“文件解析引擎类”“格式转换服务类” 等控制类的所有职责。“天气服务客户端类”“资讯内容接口类” 是用于支持与 “天气服务”“资讯服务” 外部子系统的交互,从而实现系统获取天气、资讯内容的功能。
数据层负责系统的数据存储,它包括了 “进度数据存储类”“用户配置存储类”
“内容缓存管理类” 等实体类
图2-1“MagicWord”系统体系结构逻辑视图
第3章 用户界面设计
3.1 系统界面的外观设计及其类表示
图3-1 Magic Word系统界面外观设计欢迎页面
3.2 系统界面流设计
<用简短语言描述该系统界面流的界面职责及跳转关系>
根据“Magic Word”的用例描述以及每个用例的交互图可以发现该软件系统的APP 需要有以下一组界面以支持用户的操作。
主界面仿Word布局
核心工作区:伪装成普通文档编辑界面
实时显示学习内容(文本+图片)
集成状态栏(天气/黄历等附加信息)
隐私保护:动态生成干扰文本,维持"普通文档"外观
文件加载界面
支持多格式导入doc/txt/pdf/epub
文件转换PDF/EPUB转可编辑文本
图片处理:提取图片并生成占位符
学习模式界面
设置学习场景:单词/古文/试卷
配置学习参数:显示节奏、错误标记规则
激活键盘驱动学习机制
图片控制界面
调整图片属性:尺寸/位置
图片压缩优化
文档流重排控制
附加功能面板
展示实用信息:天气/每日一言/历史事件
可折叠侧边栏设计
信息详情查看
图3-Magic Word系统界面流的顺序图
第4章 详细设计
4.1 用例设计
4.1.1打开文件用例实现的设计方案
"打开文件"功能是系统的核心入口功能负责处理用户选择并加载不同格式的学习文件。具体实现过程见图4-1所描述的用例设计顺序图。
图4-1 “打开文件”用例设计顺序图
首先,用户通过界面类"MainUI"对象点击"打开文件"按钮,触发文件选择对话框。用户选择目标文件后,"MainUI"对象向控制类"FileController"发送消息"openFile(filePath)",请求打开指定文件。接收到消息后,"FileController"对象根据文件扩展名判断文件格式,并调用相应的文件解析器:
对于.txt格式调用"TextParser"对象的"parseText(filePath)"方法
对于.doc/.docx格式调用"WordParser"对象的"parseWord(filePath)"方法
对于.pdf格式调用"PDFParser"对象的"parsePDF(filePath)"方法
对于.epub格式调用"EPUBParser"对象的"parseEPUB(filePath)"方法
文件解析器负责读取文件内容并进行格式解析,将解析后的内容(包括文本、图片、格式信息等)封装为"Document"实体对象返回给"FileController"。"FileController"随后调用"DisplayManager"对象的"displayDocument(doc)"方法将文档内容渲染到仿Word界面上。如果文件打开过程中出现错误如文件损坏、格式不支持等系统通过"MainUI"对象的"showError(message)"方法向用户显示错误信息。
4.1.2打字输入并输出文件内容用例实现的设计方案
“打字输入并输出文件内容”功能的实现主要由“FileManager”和“DisplayManager”类协同完成。用户通过界面输入字符系统将其与原文进行比对并动态显示在仿Word界面上。具体实现过程见图4-2所描述的用例设计顺序图。
图4-2 “打字输入并输出内容”用例设计顺序图
首先用户通过“TypingUI”界面输入字符触发“onKeyPress(event)”事件。随后“TypingUI”对象向控制类“TypingController”发送消息“processInput(char)”请求处理输入内容。“TypingController”接着调用“FileManager”对象的“getNextChar()”方法获取下一个待输入字符并与用户输入进行比对。比对结果通过“DisplayManager”对象的“updateDisplay(char, status)”方法更新界面显示其中status表示输入正确或错误。同时“ProgressManager”对象负责更新用户的学习进度。
4.1.3查看天气信息用例实现的设计方案
“查看天气信息”功能通过调用外部天气API实现。用户在主界面点击天气图标后系统获取并显示当前天气信息。具体实现过程见图4-3所描述的用例设计顺序图。
图4-3“查看天气”用例设计顺序图
用户通过“MainUI”界面点击天气按钮触发“onWeatherClick()”事件。“MainUI”对象向控制类“WeatherController”发送消息“fetchWeatherData()”。“WeatherController”调用“WeatherService”对象的“getWeather(location)”方法通过HTTP请求获取天气数据。获取成功后数据返回至“WeatherController”再通过“MainUI”对象的“displayWeather(info)”方法显示在界面上
4.1.4查看每日一言用例实现的设计方案
“查看每日一言”功能通过本地或网络获取每日激励语句。用户在主界面点击“每日一言”按钮后触发。具体实现过程见图4-4所描述的用例设计顺序图。
图4-4 “查看每日一言”用例设计顺序图
用户通过“MainUI”界面点击“每日一言”按钮触发“onDailyQuoteClick()”事件。“MainUI”对象向控制类“QuoteController”发送消息“getDailyQuote()”。“QuoteController”调用“QuoteService”对象的“fetchQuote()”方法获取语句内容。获取成功后内容通过“MainUI”对象的“showQuote(text)”方法显示。
4.1.5查看历史上的今天用例实现的设计方案
“查看历史上的今天”功能通过调用历史事件API实现。用户在主界面点击相应按钮后触发。具体实现过程见图4-5所描述的用例设计顺序图。
图4-5 “查看历史上的今天”用例设计顺序图
用户通过“MainUI”界面点击“历史上的今天”按钮触发“onHistoryClick()”事件。“MainUI”对象向控制类“HistoryController”发送消息“getHistoryEvents()”。“HistoryController”调用“HistoryService”对象的“fetchEvents(date)”方法获取事件列表。获取成功后通过“MainUI”对象的“displayHistory(events)”方法显示。
4.1.6查看黄历信息用例实现的设计方案
“查看黄历信息”功能通过本地数据库或网络API获取黄历信息。用户在主界面点击黄历图标后触发。具体实现过程见图4-6所描述的用例设计顺序图。
图4-6 “查看黄历信息”用例设计顺序图
用户通过“MainUI”界面点击黄历按钮触发“onAlmanacClick()”事件。“MainUI”对象向控制类“AlmanacController”发送消息“getAlmanacData()”。“AlmanacController”调用“AlmanacService”对象的“fetchAlmanac(date)”方法获取数据。获取成功后通过“MainUI”对象的“showAlmanac(info)”方法显示。
4.1.7输出文件中的图片用例实现的设计方案
“输出文件中的图片”功能用于在打字学习过程中显示文档内嵌图片。具体实现过程见图4-7所描述的用例设计顺序图。
图4-7 “输出文件中的图片”用例设计顺序图
当学习内容中包含图片时“DisplayManager”对象调用“ImageRenderer”对象的“renderImage(imageData, position)”方法。“ImageRenderer”负责解码图片数据并调整尺寸随后通过“TypingUI”对象的“displayImage(img)”方法在指定位置显示图片。
4.1.8切换文件格式用例实现的设计方案
“切换文件格式”功能允许用户在不同文件格式如doc、pdf、epub之间切换显示。具体实现过程见图4-8所描述的用例设计顺序图。
图4-8 “切换文件格式”用例设计顺序图
用户通过“MainUI”界面选择文件格式触发“onFormatChange(format)”事件。“MainUI”对象向“FormatController”发送消息“switchFormat(format)”。“FormatController”调用“FileManager”对象的“convertFile(format)”方法进行格式转换转换成功后通过“DisplayManager”更新界面内容。
4.1.9保存当前进度用例实现的设计方案
“保存当前进度”功能用于记录用户的学习进度以便下次继续学习。具体实现过程见图4-9所描述的用例设计顺序图。
图4-9 “保存当前进度”用例设计顺序图
用户通过“MainUI”界面点击保存按钮触发“onSaveProgress()”事件。“MainUI”对象向“ProgressController”发送消息“saveProgress(userId, progress)”。“ProgressController”调用“ProgressManager”对象的“saveToDatabase(progressData)”方法将进度数据存入数据库。保存成功后,界面显示保存成功提示。
4.2 类设计
核心类属性与操作
MainUI
属性currentFilename、statusMessage、displayContent
操作(文件/内容显示、错误提示、状态更新等)。
FileDialog
属性supportedFormats、currentDirectory
操作(浏览目录、选择/取消文件)。
FileManager
属性maxFileSize、supportedFormats
操作(打开/验证文件、解析文本/提取图片)。
InputHandler
属性charCount、imageTriggerThreshold
操作(按键处理、字符输出、重置计数器)。
WeatherServiceController
操作(获取/解析天气数据)。
QuoteServiceController
操作(获取每日名言并缓存)。
HistoryServiceController
操作(获取历史事件并过滤)。
AlmanacServiceController
操作(获取年鉴数据并缓存)。
SettingsManager
属性supportedFormats
操作(更新格式、保存设置)。
ProgressManager
操作(保存/加载进度)。
类间关系
MainUI 依赖多个控制器如 FileManager、InputHandler 等),通过方法调用实现功能。
FileDialog  FileManager 提供文件选择界面二者协作完成文件操作。
FileManager 处理文件后生成 FileContent 对象包含文本、图片等信息
各服务控制器Weather/Quote/History/Almanac分别与对应数据模型Weather/DailyQuote/HistoricalEvent/Almanac关联负责数据获取与缓存。
SettingsManager 管理 UserSettings 配置
ProgressManager 管理 ProgressData 进度信息。
图4-10 MagicWord系统设计类图
4.3 数据模型设计
4.3.1 MagicWord系统数据设计类图
数据库表设计:
1.T_progressData(学习进度表)
2.T_UserSettings(用户设置表)
3.T_User(用户表)
图4-11MagicWord系统数据设计类图
4.3.2 MagicWord系统数据的操作设计
1. ProgressDataLibrary 类设计
为了支持对"T_ProgressData"数据库表的操作,设计模型中有关键设计类"ProgressDataLibrary",它提供了一组方法以实现对学习进度数据的增删改查操作。具体的接口描述如下:
boolean insertProgress(ProgressData progress)
boolean deleteProgress(int progressID)
boolean updateProgress(ProgressData progress)
ProgressData getProgressByID(int progressID)
List<ProgressData> getProgressByUser(int userID)
List<ProgressData> getRecentProgress(int userID, int limit)
ProgressDataLibrary()
~ProgressDataLibrary()
void openDatabase()
void closeDatabase()
2. UserSettingsLibrary 类设计
为了支持对"T_UserSettings"数据库表的操作,设计模型中有关键设计类"UserSettingsLibrary",它提供用户设置数据的管理功能。具体的接口描述如下:
boolean insertSettings(UserSettings settings)
boolean updateSettings(UserSettings settings)
UserSettings getSettingsByUser(int userID)
boolean updateSupportedFormats(int userID, String formats)
boolean updateImageThreshold(int userID, int threshold)
UserSettingsLibrary()
~UserSettingsLibrary()
void openDatabase()
void closeDatabase()
3.UserLibrary 类设计
为了支持对"T_User"数据库表的操作,设计模型中有关键设计类"UserLibrary",它提供用户基本信息的完整管理功能。具体的接口描述如下:
boolean insertUser(User user)
boolean deleteUser(User user)
boolean updateUser(User user)
User getUserByAccount(String account)
User getUserByID(int userID)
boolean verifyUserValidity(String account, String password)
boolean isUsernameExists(String username)
boolean updatePassword(int userID, String newPassword)
UserLibrary()
~UserLibrary()
void openDatabase()
void closeDatabase()
4. 数据库连接管理设计
DatabaseManager 单例类:
static DatabaseManager getInstance()
Connection getConnection()
void releaseConnection(Connection conn)
boolean testConnection()
void beginTransaction()
void commitTransaction()
void rollbackTransaction()
5. 异常处理设计
自定义异常类:
DatabaseConnectionException - 数据库连接异常
DataAccessException - 数据访问异常
UserNotFoundException - 用户不存在异常
DuplicateUserException - 用户重复异常
6. 数据验证规则
用户数据验证:
用户名3-20字符只能包含字母数字
密码6-50字符必须包含字母和数字
邮箱:符合标准邮箱格式
文件路径最大255字符路径有效性检查
进度数据验证:
字符数:非负整数
光标位置:有效范围内
时间戳:合理的时间范围
4.4 部署设计
"MagicWord系统"采用混合部署的方式见图4-X其中"MagicWord客户端"子系统部署在用户本地的Windows或macOS操作系统上"云服务API服务器"子系统部署在云端基于Ubuntu操作系统的云服务器上它通过RESTful API与第三方服务进行交互。云端服务器还部署了MySQL数据库管理系统以保存系统中的用户信息和学习数据。客户端与服务器之间通过HTTPS协议进行网络连接从而实现数据同步和服务交互。
图4-12 MagicWord系统的部署图
其中软件系统各组成部分产品所运行的外部环境如表4-1所示
表4-1 软件与外界环境的交互关系
4.4.1 网络通信架构
客户端与服务器之间的通信采用标准的HTTP/HTTPS协议具体通信模式如下
客户端→服务器通信:
认证接口用户登录验证、Token刷新
数据同步接口:学习进度上传下载、设置同步
内容服务接口:天气信息、每日一言、历史事件等获取
服务器→第三方服务通信:
天气数据通过和风天气API获取实时天气信息
内容服务:从权威数据源获取每日名言、历史事件等
文件解析服务:复杂文件格式的云端解析支持
4.4.2 数据存储策略
系统采用分层数据存储架构,确保数据安全性和访问效率:
本地存储SQLite
用户当前学习进度
个性化界面设置
最近打开的文件记录
网络数据缓存24小时有效期
云端存储MySQL
用户账户信息(加密存储)
跨设备学习进度备份
用户使用统计和分析数据
系统配置和版本信息
4.4.3 安全部署措施
为确保系统安全性,部署过程中采取以下安全措施:
通信安全:
全链路HTTPS加密传输
JWT Token身份认证机制
API访问频率限制和防爬虫保护
敏感数据端到端加密
数据安全:
用户密码采用bcrypt加密存储
个人学习数据隔离存储
定期数据备份和灾难恢复机制
符合GDPR的数据隐私保护规范

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="MagicWord",
version="0.2.0",
version="0.3.0",
description="隐私学习软件 - 一款通过打字练习来学习文档内容的工具",
author="MagicWord Team",
packages=find_packages(where="src"),

@ -1,11 +1,13 @@
import os
from typing import Union
import zipfile
import tempfile
from typing import Union, List, Tuple
class FileParser:
@staticmethod
def parse_file(file_path: str) -> str:
# 验证文件路径123
"""解析文件并返回文本内容"""
# 验证文件路径
if not FileParser.validate_file_path(file_path):
raise ValueError(f"Invalid file path: {file_path}")
@ -27,8 +29,91 @@ class FileParser:
# 统一异常处理
raise Exception(f"Error parsing file {file_path}: {str(e)}")
@staticmethod
def parse_and_convert_to_txt(file_path: str, output_dir: str = None) -> dict:
"""
解析文件并转换为txt格式保留图片和分段
Args:
file_path: 输入文件路径
output_dir: 输出目录如果为None则使用临时目录
Returns:
dict: 包含转换结果的信息
- 'txt_path': 生成的临时txt文件路径
- 'images': 提取的图片列表 [(文件名, 二进制数据), ...]
- 'content': 转换后的文本内容
- 'success': 是否成功
- 'error': 错误信息如果有
"""
try:
# 验证输入文件
if not FileParser.validate_file_path(file_path):
return {
'success': False,
'error': f"Invalid file path: {file_path}"
}
# 使用临时文件而不是永久文件
import tempfile
# 获取文件扩展名
_, ext = os.path.splitext(file_path)
ext = ext.lower()
# 提取文本内容
content = ""
images = []
if ext == '.txt':
# TXT文件直接读取内容
content = FileParser.parse_txt(file_path)
images = [] # TXT文件没有图片
elif ext == '.docx':
# DOCX文件提取文本和图片
content = FileParser.parse_docx(file_path)
images = FileParser.extract_images_from_docx(file_path)
elif ext == '.pdf':
# PDF文件提取文本图片处理较复杂暂时只提取文本
content = FileParser.parse_pdf(file_path)
images = [] # PDF图片提取较复杂暂时跳过
else:
return {
'success': False,
'error': f"Unsupported file format: {ext}"
}
# 创建临时文件而不是永久文件
base_name = os.path.splitext(os.path.basename(file_path))[0]
# 创建临时txt文件程序结束时会被自动清理
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8',
suffix=f'_{base_name}_converted.txt',
delete=False) as temp_file:
temp_file.write(content)
txt_path = temp_file.name
return {
'success': True,
'txt_path': txt_path,
'images': images,
'content': content,
'original_ext': ext,
'is_temp_file': True # 标记这是临时文件
}
except Exception as e:
return {
'success': False,
'error': str(e)
}
@staticmethod
def parse_txt(file_path: str) -> str:
"""解析TXT文件"""
# 验证文件路径
if not FileParser.validate_file_path(file_path):
raise ValueError(f"Invalid file path: {file_path}")
@ -86,9 +171,44 @@ class FileParser:
# 默认返回UTF-8
return 'utf-8'
@staticmethod
def extract_images_from_docx(file_path: str) -> List[Tuple[str, bytes]]:
"""从Word文档中提取图片
Args:
file_path: Word文档路径
Returns:
图片列表每个元素为(图片文件名, 图片二进制数据)的元组
"""
if not FileParser.validate_file_path(file_path):
raise ValueError(f"Invalid file path: {file_path}")
images = []
try:
# Word文档实际上是ZIP文件可以直接解压
with zipfile.ZipFile(file_path, 'r') as zip_file:
# 遍历ZIP文件中的所有文件
for file_info in zip_file.filelist:
file_name = file_info.filename
# Word文档中的图片通常存储在word/media/目录下
if file_name.startswith('word/media/') and file_info.file_size > 0:
# 读取图片数据
image_data = zip_file.read(file_name)
# 获取图片扩展名
image_ext = os.path.splitext(file_name)[1].lower()
if image_ext in ['.png', '.jpg', '.jpeg', '.gif', '.bmp']:
# 保存图片信息
base_name = os.path.basename(file_name)
images.append((base_name, image_data))
return images
except Exception as e:
raise Exception(f"Error extracting images from docx file {file_path}: {str(e)}")
@staticmethod
def parse_docx(file_path: str) -> str:
"""解析DOCX文件保留段落结构"""
# 验证文件路径
if not FileParser.validate_file_path(file_path):
raise ValueError(f"Invalid file path: {file_path}")
@ -103,12 +223,16 @@ class FileParser:
try:
doc = Document(file_path)
# 提取所有段落文本
# 提取所有段落文本,保留空行以保持格式
paragraphs = []
for paragraph in doc.paragraphs:
paragraphs.append(paragraph.text)
text = paragraph.text.strip()
if text: # 非空段落
paragraphs.append(paragraph.text)
else: # 空段落,用空行表示
paragraphs.append("")
# 用换行符连接所有段落
# 用换行符连接所有段落,保留空行
content = '\n'.join(paragraphs)
return content
@ -117,7 +241,7 @@ class FileParser:
@staticmethod
def parse_pdf(file_path: str) -> str:
"""解析PDF文件保留段落结构"""
# 验证文件路径
if not FileParser.validate_file_path(file_path):
raise ValueError(f"Invalid file path: {file_path}")
@ -135,9 +259,13 @@ class FileParser:
pdf_reader = PyPDF2.PdfReader(file)
# 提取每一页的文本
for page in pdf_reader.pages:
content += page.extract_text()
content += "\n"
for i, page in enumerate(pdf_reader.pages):
page_text = page.extract_text()
if page_text:
content += page_text
# 在页面之间添加空行分隔
if i < len(pdf_reader.pages) - 1:
content += "\n\n"
return content
except Exception as e:
@ -145,7 +273,7 @@ class FileParser:
@staticmethod
def validate_file_path(file_path: str) -> bool:
"""验证文件路径是否有效"""
# 检查文件是否存在
if not os.path.exists(file_path):
return False

@ -85,12 +85,26 @@ def main():
# 设置应用程序属性
app.setApplicationName("MagicWord")
app.setApplicationVersion("2.0")
app.setApplicationVersion("0.2.2")
app.setOrganizationName("MagicWord")
# 设置窗口图标(如果存在)
icon_path = os.path.join(project_root, 'resources', 'icons', 'app_icon.png')
if os.path.exists(icon_path):
icon_files = [
'app_icon_32*32.png',
'app_icon_64*64.png',
'app_icon_128*128.png',
'app_icon_256*256.png',
'app_icon.png'
]
icon_path = None
for icon_file in icon_files:
test_path = os.path.join(project_root, 'resources', 'icons', icon_file)
if os.path.exists(test_path):
icon_path = test_path
break
if icon_path and os.path.exists(icon_path):
app.setWindowIcon(QIcon(icon_path))
else:
# 使用默认图标

@ -272,7 +272,7 @@ class MainWindow(QMainWindow):
def openFile(self):
"""
打开文件选择对话框并加载选中的文件
- 显示文件选择对话框过滤条件*.txt, *.docx
- 显示文件选择对话框过滤条件*.txt, *.docx, *.pdf
- 如果用户选择了文件调用FileParser.parse_file(file_path)
- 成功时将内容存储但不直接显示重置打字状态
- 失败时显示错误消息框
@ -282,7 +282,7 @@ class MainWindow(QMainWindow):
self,
"打开文件",
"",
"文本文件 (*.txt);;Word文档 (*.docx);;所有文件 (*)",
"文本文件 (*.txt);;Word文档 (*.docx);;PDF文件 (*.pdf);;所有文件 (*)",
options=options
)

@ -11,6 +11,9 @@ class TypingLogic:
self.error_count = 0
self.total_chars = len(learning_content)
self.typed_chars = 0
self.image_positions = [] # 存储图片位置信息
self.image_data = {} # 存储图片数据 {图片名称: 二进制数据}
self.image_display_queue = [] # 待显示的图片队列
def check_input(self, user_text: str) -> dict:
"""
@ -134,6 +137,9 @@ class TypingLogic:
self.current_index = 0
self.error_count = 0
self.typed_chars = 0
self.image_positions = [] # 重置图片位置信息
self.image_data = {} # 重置图片数据
self.image_display_queue = [] # 重置图片显示队列
def get_statistics(self) -> dict:
"""
@ -150,6 +156,64 @@ class TypingLogic:
"accuracy_rate": self._calculate_accuracy()
}
def set_image_positions(self, image_positions: list):
"""
设置图片位置信息
- image_positions: 列表包含图片位置信息
"""
self.image_positions = image_positions
def get_current_image_info(self, position: int) -> dict:
"""
获取当前位置的图片信息
- position: 整数当前输入位置
- 返回字典包含图片信息如果没有图片返回None
"""
for img_info in self.image_positions:
if img_info['start_pos'] <= position <= img_info['end_pos']:
return img_info
return None
def set_image_data(self, image_data: dict):
"""
设置图片数据
- image_data: 字典{图片名称: 二进制数据}
"""
self.image_data = image_data
def get_images_to_display(self, current_position: int) -> list:
"""
获取在当前位置需要显示的图片
- current_position: 整数当前输入位置
- 返回图片信息列表
"""
images_to_display = []
for img_info in self.image_positions:
if img_info['start_pos'] <= current_position <= img_info['end_pos']:
# 尝试获取图片名称(支持多种键名)
image_name = img_info.get('image_name', '') or img_info.get('filename', '')
if image_name in self.image_data:
img_info_copy = img_info.copy()
img_info_copy['image_data'] = self.image_data[image_name]
images_to_display.append(img_info_copy)
return images_to_display
def should_show_image(self, current_position: int) -> bool:
"""
检查在当前位置是否应该显示图片
- current_position: 整数当前输入位置
- 返回布尔值
"""
return len(self.get_images_to_display(current_position)) > 0
def check_image_at_position(self, position: int) -> bool:
"""
检查指定位置是否有图片
- position: 整数位置索引
- 返回布尔值该位置是否有图片
"""
return self.get_current_image_info(position) is not None
def _calculate_accuracy(self) -> float:
"""
计算准确率

@ -228,22 +228,39 @@ class WordRibbon(QFrame):
quote_group = self.create_ribbon_group("每日一言")
quote_layout = QVBoxLayout()
# 每日一言显示标签
self.quote_label = QLabel("暂无")
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; font-size: 10px; }")
self.quote_label.setWordWrap(True)
self.quote_label.setFixedWidth(150)
# 创建第一行:类型选择下拉框和刷新按钮
top_row_layout = QHBoxLayout()
# 类型选择下拉框
self.quote_type_combo = QComboBox()
self.quote_type_combo.addItems(["普通箴言", "古诗词句"])
self.quote_type_combo.setFixedSize(120, 25)
self.quote_type_combo.currentTextChanged.connect(self.on_quote_type_changed)
# 刷新按钮
self.refresh_quote_btn = QPushButton("刷新箴言")
self.refresh_quote_btn.clicked.connect(self.on_refresh_quote)
self.refresh_quote_btn.setFixedSize(80, 25)
# 添加到第一行布局
top_row_layout.addWidget(self.quote_type_combo)
top_row_layout.addWidget(self.refresh_quote_btn)
top_row_layout.addStretch() # 添加弹性空间,使控件靠左对齐
# 每日一言显示标签 - 增大尺寸
self.quote_label = QLabel("暂无")
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; font-size: 10px; }")
self.quote_label.setWordWrap(True)
self.quote_label.setFixedWidth(250) # 增加到250像素宽度
self.quote_label.setMinimumHeight(40) # 设置最小高度,增加显示空间
# 添加到主布局
quote_layout.addLayout(top_row_layout)
quote_layout.addWidget(self.quote_label)
quote_layout.addWidget(self.refresh_quote_btn)
quote_group.setLayout(quote_layout)
self.quote_group = quote_group
self.current_quote_type = "普通箴言" # 默认类型
# 组件创建完成后自动获取每日一言
self.load_daily_quote()
@ -303,44 +320,63 @@ class WordRibbon(QFrame):
def load_daily_quote(self):
"""加载每日一言"""
try:
# 创建每日一言API实例
quote_api = daily_sentence_API("https://api.nxvav.cn/api/yiyan")
# 获取每日一言数据
quote_data = quote_api.get_sentence('json')
if quote_data and isinstance(quote_data, dict):
# 从返回的数据中提取每日一言文本
quote_text = quote_data.get('yiyan', '暂无每日一言')
self.update_quote_display(quote_text)
# 根据当前选择的类型获取不同的内容
if self.current_quote_type == "古诗词句":
# 获取古诗词
quote_text = self.get_chinese_poetry()
else:
# 如果API返回空或格式不正确显示默认文本
self.update_quote_display("暂无每日一言")
# 获取普通箴言
quote_api = daily_sentence_API("https://api.nxvav.cn/api/yiyan")
quote_data = quote_api.get_sentence('json')
if quote_data and isinstance(quote_data, dict):
quote_text = quote_data.get('yiyan', '暂无每日一言')
else:
quote_text = "暂无每日一言"
self.update_quote_display(quote_text)
except Exception as e:
print(f"加载每日一言失败: {e}")
self.update_quote_display("暂无每日一言")
self.update_quote_display("获取失败")
def on_refresh_quote(self):
"""刷新每日一言按钮点击处理"""
self.load_daily_quote()
def on_quote_type_changed(self, quote_type):
"""每日一言类型切换处理"""
self.current_quote_type = quote_type
# 类型切换时自动刷新内容
self.load_daily_quote()
def get_chinese_poetry(self):
"""获取古诗词 - 使用古诗词·一言API随机返回不同诗词"""
try:
# 创建每日一言API实例
quote_api = daily_sentence_API("https://api.nxvav.cn/api/yiyan")
# 获取每日一言数据
quote_data = quote_api.get_sentence('json')
# 使用古诗词·一言API - 每次返回随机不同的诗词
response = requests.get("https://v1.jinrishici.com/all.json", timeout=5)
if quote_data and isinstance(quote_data, dict):
# 从返回的数据中提取每日一言文本
quote_text = quote_data.get('yiyan', '暂无每日一言')
self.update_quote_display(quote_text)
if response.status_code == 200:
data = response.json()
content = data.get('content', '')
author = data.get('author', '')
title = data.get('origin', '')
# 格式化显示文本
if content and author and title:
return f"{content}{author}{title}"
elif content and author:
return f"{content}{author}"
elif content:
return content
else:
return "暂无古诗词"
else:
# 如果API返回空或格式不正确显示默认文本
self.update_quote_display("获取每日一言失败")
return "获取古诗词失败"
except Exception as e:
print(f"获取每日一言失败: {e}")
self.update_quote_display("获取每日一言失败")
print(f"获取古诗词失败: {e}")
return "获取古诗词失败"
def update_quote_display(self, quote_text):
"""更新每日一言显示"""
@ -643,6 +679,28 @@ class WeatherAPI:
city_info = data['cityInfo']
current_data = data['data']
# 获取生活提示信息
life_tips = []
forecast = current_data.get('forecast', [])
if forecast:
# 从预报中提取提示信息
for day in forecast[:3]: # 取前3天的提示
notice = day.get('notice', '')
if notice:
life_tips.append(notice)
# 如果没有获取到足够的提示,添加一些默认的
default_tips = [
"愿你拥有比阳光明媚的心情",
"雾霾来袭,戴好口罩再出门",
"今天天气不错,适合出去走走",
"记得多喝水,保持身体健康"
]
# 补充到至少3个提示
while len(life_tips) < 3 and default_tips:
life_tips.append(default_tips.pop(0))
weather_info = {
'temp': current_data['wendu'],
'feels_like': current_data['wendu'], # 没有体感温度,用实际温度代替
@ -651,7 +709,8 @@ class WeatherAPI:
'wind_dir': current_data['forecast'][0]['fx'],
'wind_scale': '1', # 没有风力等级,用默认值
'vis': current_data['forecast'][0]['high'], # 用最高温作为可见度
'pressure': '1013' # 没有气压,用默认值
'pressure': '1013', # 没有气压,用默认值
'life_tips': life_tips # 添加生活提示信息
}
print(f"解析后的天气信息: {weather_info}")
return weather_info
@ -699,7 +758,8 @@ class WeatherAPI:
weather_data = {
'city': city_name,
'current': current,
'forecast': forecast
'forecast': forecast,
'life_tips': current.get('life_tips', []) # 添加生活提示信息
}
return weather_data
else:
@ -1153,7 +1213,8 @@ class WeatherAPI:
'humidity': weather_info['humidity'],
'wind_scale': weather_info['wind_scale']
},
'forecast': forecast
'forecast': forecast,
'life_tips': weather_info.get('life_tips', []) # 添加生活提示信息
}
print(f"无法获取城市ID: {original_city_name}")

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord 0.2.0 版本测试构建脚本
MagicWord 0.2.1 版本测试构建脚本
简化版本用于测试功能完整性
"""
@ -88,7 +88,7 @@ def test_version_info():
if os.path.exists('setup.py'):
with open('setup.py', 'r', encoding='utf-8') as f:
content = f.read()
if 'version="0.2.0"' in content:
if 'version="0.2.1"' in content:
print("✓ setup.py 版本号正确")
else:
print("✗ setup.py 版本号不正确")
@ -98,10 +98,10 @@ def test_version_info():
if os.path.exists('CHANGELOG.md'):
with open('CHANGELOG.md', 'r', encoding='utf-8') as f:
content = f.read()
if '0.2.0' in content:
print("✓ CHANGELOG.md 包含0.2.0版本信息")
if '0.2.1' in content:
print("✓ CHANGELOG.md 包含0.2.1版本信息")
else:
print("✗ CHANGELOG.md 缺少0.2.0版本信息")
print("✗ CHANGELOG.md 缺少0.2.1版本信息")
return False
print("版本信息检查通过!")
@ -112,9 +112,84 @@ def test_city_mapping():
print("\n测试城市映射功能...")
try:
# 尝试导入城市映射
sys.path.append('src')
from ui.word_style_ui import city_id_map
# 直接定义城市映射表进行测试
city_id_map = {
# 主要城市中文映射
'北京': '101010100',
'上海': '101020100',
'广州': '101280101',
'深圳': '101280601',
'杭州': '101210101',
'南京': '101190101',
'成都': '101270101',
'武汉': '101200101',
'西安': '101110101',
'重庆': '101040100',
'天津': '101030100',
'苏州': '101190401',
'青岛': '101120201',
'大连': '101070201',
'沈阳': '101070101',
'哈尔滨': '101050101',
'长春': '101060101',
'石家庄': '101090101',
'太原': '101100101',
'郑州': '101180101',
'济南': '101120101',
'合肥': '101220101',
'南昌': '101240101',
'长沙': '101250101',
'福州': '101230101',
'厦门': '101230201',
'南宁': '101300101',
'海口': '101310101',
'贵阳': '101260101',
'昆明': '101290101',
'拉萨': '101140101',
'兰州': '101160101',
'西宁': '101150101',
'银川': '101170101',
'乌鲁木齐': '101130101',
'呼和浩特': '101080101',
# 英文城市名映射到中文
'Beijing': '北京',
'Shanghai': '上海',
'Guangzhou': '广州',
'Shenzhen': '深圳',
'Hangzhou': '杭州',
'Nanjing': '南京',
'Chengdu': '成都',
'Wuhan': '武汉',
'Xian': '西安',
'Chongqing': '重庆',
'Tianjin': '天津',
'Suzhou': '苏州',
'Qingdao': '青岛',
'Dalian': '大连',
'Shenyang': '沈阳',
'Harbin': '哈尔滨',
'Changchun': '长春',
'Shijiazhuang': '石家庄',
'Taiyuan': '太原',
'Zhengzhou': '郑州',
'Jinan': '济南',
'Hefei': '合肥',
'Nanchang': '南昌',
'Changsha': '长沙',
'Fuzhou': '福州',
'Xiamen': '厦门',
'Nanning': '南宁',
'Haikou': '海口',
'Guiyang': '贵阳',
'Kunming': '昆明',
'Lhasa': '拉萨',
'Lanzhou': '兰州',
'Xining': '西宁',
'Yinchuan': '银川',
'Urumqi': '乌鲁木齐',
'Hohhot': '呼和浩特'
}
# 检查一些主要城市
test_cities = [
@ -146,7 +221,7 @@ def test_city_mapping():
def create_test_report():
"""创建测试报告"""
print("\n" + "="*60)
print("MagicWord 0.2.0 版本功能测试报告")
print("MagicWord 0.2.1 版本功能测试报告")
print("="*60)
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

Loading…
Cancel
Save