添加文件解析能力
This commit is contained in:
@@ -41,6 +41,9 @@ offerpie_python_ai/
|
|||||||
│ ├─ user_func_permission_stock.py # 用户功能权限库存表(bg_user_func_permission_stock)
|
│ ├─ user_func_permission_stock.py # 用户功能权限库存表(bg_user_func_permission_stock)
|
||||||
│ └─ user_func_usage_log.py # 用户功能使用记录表(bg_user_func_usage_log)
|
│ └─ user_func_usage_log.py # 用户功能使用记录表(bg_user_func_usage_log)
|
||||||
│
|
│
|
||||||
|
├─ tool/ # **工具层**(无状态、无业务依赖的通用工具)
|
||||||
|
│ └─ file_parser.py # 文件解析工具(PDF/Word/TXT → 纯文本,parse_to_text 入口方法)
|
||||||
|
│
|
||||||
└─ services/ # **业务逻辑层**
|
└─ services/ # **业务逻辑层**
|
||||||
└─ func_permission_service.py # 功能权限服务(校验+扣减+回退,逻辑与Java端一致)
|
└─ func_permission_service.py # 功能权限服务(校验+扣减+回退,逻辑与Java端一致)
|
||||||
```
|
```
|
||||||
@@ -53,6 +56,7 @@ offerpie_python_ai/
|
|||||||
| **ai** | AI 模型管理,封装多供应商 LLM 实例创建,基于 LangChain ChatOpenAI | `LLM` 枚举(火山引擎:doubao/deepseek,心缘:gpt-4o/claude) |
|
| **ai** | AI 模型管理,封装多供应商 LLM 实例创建,基于 LangChain ChatOpenAI | `LLM` 枚举(火山引擎:doubao/deepseek,心缘:gpt-4o/claude) |
|
||||||
| **api** | REST API 路由定义 | `health.py`(健康检查) |
|
| **api** | REST API 路由定义 | `health.py`(健康检查) |
|
||||||
| **models** | SQLAlchemy ORM 模型,与 Java 端共享同一数据库 | `FuncPermission`、`UserFuncPermissionStock`、`UserFuncUsageLog` |
|
| **models** | SQLAlchemy ORM 模型,与 Java 端共享同一数据库 | `FuncPermission`、`UserFuncPermissionStock`、`UserFuncUsageLog` |
|
||||||
|
| **tool** | 无状态通用工具,不依赖数据库/Redis/用户上下文 | `file_parser.py`(PDF/Word/TXT 文件解析为纯文本) |
|
||||||
| **services** | 业务逻辑实现 | `FuncPermissionService`(功能权限校验、扣减、回退) |
|
| **services** | 业务逻辑实现 | `FuncPermissionService`(功能权限校验、扣减、回退) |
|
||||||
|
|
||||||
## 3️⃣ 技术栈
|
## 3️⃣ 技术栈
|
||||||
@@ -67,6 +71,7 @@ offerpie_python_ai/
|
|||||||
| **鉴权** | PyJWT | JWT 解析,与 Java 端共享同一 jwt_secret |
|
| **鉴权** | PyJWT | JWT 解析,与 Java 端共享同一 jwt_secret |
|
||||||
| **数据处理** | Pandas + NumPy | 数据分析与处理 |
|
| **数据处理** | Pandas + NumPy | 数据分析与处理 |
|
||||||
| **HTTP** | httpx | 异步 HTTP 客户端 |
|
| **HTTP** | httpx | 异步 HTTP 客户端 |
|
||||||
|
| **文件解析** | pdfplumber + python-docx | PDF 和 Word 文件内容提取 |
|
||||||
|
|
||||||
## 4️⃣ 中间件执行链(由外到内)
|
## 4️⃣ 中间件执行链(由外到内)
|
||||||
| 顺序 | 中间件 | 职责 |
|
| 顺序 | 中间件 | 职责 |
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -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")
|
||||||
@@ -39,6 +39,10 @@ numpy>=1.26.0
|
|||||||
python-multipart>=0.0.9
|
python-multipart>=0.0.9
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0
|
||||||
|
|
||||||
|
# 文件解析
|
||||||
|
pdfplumber>=0.11.0
|
||||||
|
python-docx>=1.1.0
|
||||||
|
|
||||||
# 测试
|
# 测试
|
||||||
pytest>=8.0.0
|
pytest>=8.0.0
|
||||||
pytest-asyncio>=0.24.0
|
pytest-asyncio>=0.24.0
|
||||||
|
|||||||
Reference in New Issue
Block a user