From 4de721ffcad91c7aa58d51dd9225843d5eac29c7 Mon Sep 17 00:00:00 2001 From: zk Date: Thu, 2 Apr 2026 14:55:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kiro/steering/项目结构说明.md | 5 +++ app/tool/__init__.py | 1 + app/tool/file_parser.py | 68 ++++++++++++++++++++++++++++++++++ requirements.txt | 4 ++ 4 files changed, 78 insertions(+) create mode 100644 app/tool/__init__.py create mode 100644 app/tool/file_parser.py diff --git a/.kiro/steering/项目结构说明.md b/.kiro/steering/项目结构说明.md index 2c96671..febb406 100644 --- a/.kiro/steering/项目结构说明.md +++ b/.kiro/steering/项目结构说明.md @@ -41,6 +41,9 @@ offerpie_python_ai/ │ ├─ user_func_permission_stock.py # 用户功能权限库存表(bg_user_func_permission_stock) │ └─ user_func_usage_log.py # 用户功能使用记录表(bg_user_func_usage_log) │ + ├─ tool/ # **工具层**(无状态、无业务依赖的通用工具) + │ └─ file_parser.py # 文件解析工具(PDF/Word/TXT → 纯文本,parse_to_text 入口方法) + │ └─ services/ # **业务逻辑层** └─ func_permission_service.py # 功能权限服务(校验+扣减+回退,逻辑与Java端一致) ``` @@ -53,6 +56,7 @@ offerpie_python_ai/ | **ai** | AI 模型管理,封装多供应商 LLM 实例创建,基于 LangChain ChatOpenAI | `LLM` 枚举(火山引擎:doubao/deepseek,心缘:gpt-4o/claude) | | **api** | REST API 路由定义 | `health.py`(健康检查) | | **models** | SQLAlchemy ORM 模型,与 Java 端共享同一数据库 | `FuncPermission`、`UserFuncPermissionStock`、`UserFuncUsageLog` | +| **tool** | 无状态通用工具,不依赖数据库/Redis/用户上下文 | `file_parser.py`(PDF/Word/TXT 文件解析为纯文本) | | **services** | 业务逻辑实现 | `FuncPermissionService`(功能权限校验、扣减、回退) | ## 3️⃣ 技术栈 @@ -67,6 +71,7 @@ offerpie_python_ai/ | **鉴权** | PyJWT | JWT 解析,与 Java 端共享同一 jwt_secret | | **数据处理** | Pandas + NumPy | 数据分析与处理 | | **HTTP** | httpx | 异步 HTTP 客户端 | +| **文件解析** | pdfplumber + python-docx | PDF 和 Word 文件内容提取 | ## 4️⃣ 中间件执行链(由外到内) | 顺序 | 中间件 | 职责 | diff --git a/app/tool/__init__.py b/app/tool/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/tool/__init__.py @@ -0,0 +1 @@ + diff --git a/app/tool/file_parser.py b/app/tool/file_parser.py new file mode 100644 index 0000000..dec8555 --- /dev/null +++ b/app/tool/file_parser.py @@ -0,0 +1,68 @@ +"""文件解析工具 + +将上传的简历文件(PDF / Word / TXT)转换为纯文本字符串。 +""" + +import io + +import pdfplumber +from docx import Document + +from app.core.logger import log + + +def parse_pdf(content: bytes) -> str: + """解析 PDF 文件,提取全部页面文本""" + text_parts: list[str] = [] + with pdfplumber.open(io.BytesIO(content)) as pdf: + for page in pdf.pages: + page_text = page.extract_text() + if page_text: + text_parts.append(page_text) + return "\n".join(text_parts) + + +def parse_docx(content: bytes) -> str: + """解析 Word (.docx) 文件,提取段落和表格文本""" + doc = Document(io.BytesIO(content)) + text_parts: list[str] = [] + + # 段落 + for para in doc.paragraphs: + text = para.text.strip() + if text: + text_parts.append(text) + + # 表格 + for table in doc.tables: + for row in table.rows: + row_text = "\t".join(cell.text.strip() for cell in row.cells) + if row_text.strip(): + text_parts.append(row_text) + + return "\n".join(text_parts) + + +def parse_txt(content: bytes) -> str: + """解析 TXT 文件,自动检测编码""" + for encoding in ("utf-8", "gbk", "gb2312", "latin-1"): + try: + return content.decode(encoding) + except (UnicodeDecodeError, LookupError): + continue + return content.decode("utf-8", errors="replace") + + +def parse_to_text(filename: str, content: bytes) -> str: + """根据文件名后缀自动选择解析方法,返回纯文本""" + suffix = filename[filename.rfind("."):].lower() if "." in filename else "" + log.info(f"解析文件: {filename},类型: {suffix}") + + if suffix == ".pdf": + return parse_pdf(content) + elif suffix in (".docx", ".doc"): + return parse_docx(content) + elif suffix == ".txt": + return parse_txt(content) + else: + raise ValueError(f"不支持的文件类型: {suffix},支持: .pdf, .docx, .doc, .txt") diff --git a/requirements.txt b/requirements.txt index 4cbafb9..75b5934 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,6 +39,10 @@ numpy>=1.26.0 python-multipart>=0.0.9 python-dotenv>=1.0.0 +# 文件解析 +pdfplumber>=0.11.0 +python-docx>=1.1.0 + # 测试 pytest>=8.0.0 pytest-asyncio>=0.24.0