Files
tyapi-server/internal/shared/pdf/pdf_finder.go
2025-12-03 12:03:42 +08:00

243 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pdf
import (
"fmt"
"os"
"path/filepath"
"strings"
"go.uber.org/zap"
)
// GetDocumentationDir 获取接口文档文件夹路径
// 会在当前目录及其父目录中查找"接口文档"文件夹
func GetDocumentationDir() (string, error) {
// 获取当前工作目录
wd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("获取工作目录失败: %w", err)
}
// 搜索策略:从当前目录开始,向上查找"接口文档"文件夹
currentDir := wd
maxDepth := 10 // 增加搜索深度,确保能找到
var checkedDirs []string
for i := 0; i < maxDepth; i++ {
docDir := filepath.Join(currentDir, "接口文档")
checkedDirs = append(checkedDirs, docDir)
if info, err := os.Stat(docDir); err == nil && info.IsDir() {
absPath, err := filepath.Abs(docDir)
if err != nil {
return "", fmt.Errorf("获取绝对路径失败: %w", err)
}
return absPath, nil
}
// 尝试父目录
parentDir := filepath.Dir(currentDir)
if parentDir == currentDir {
break // 已到达根目录
}
currentDir = parentDir
}
return "", fmt.Errorf("未找到接口文档文件夹。已检查的路径: %v当前工作目录: %s", checkedDirs, wd)
}
// PDFFinder PDF文件查找服务
type PDFFinder struct {
documentationDir string
logger *zap.Logger
}
// NewPDFFinder 创建PDF查找服务
func NewPDFFinder(documentationDir string, logger *zap.Logger) *PDFFinder {
return &PDFFinder{
documentationDir: documentationDir,
logger: logger,
}
}
// FindPDFByProductCode 根据产品代码查找PDF文件
// 会在接口文档文件夹中递归搜索匹配的PDF文件
// 文件名格式应为: *_{产品代码}.pdf
func (f *PDFFinder) FindPDFByProductCode(productCode string) (string, error) {
if productCode == "" {
return "", fmt.Errorf("产品代码不能为空")
}
// 构建搜索模式:文件名以 _{产品代码}.pdf 结尾
searchPattern := fmt.Sprintf("*_%s.pdf", productCode)
f.logger.Info("开始搜索PDF文件",
zap.String("product_code", productCode),
zap.String("search_pattern", searchPattern),
zap.String("documentation_dir", f.documentationDir),
)
// 验证接口文档文件夹是否存在
if info, err := os.Stat(f.documentationDir); err != nil || !info.IsDir() {
f.logger.Error("接口文档文件夹不存在或无法访问",
zap.String("documentation_dir", f.documentationDir),
zap.Error(err),
)
return "", fmt.Errorf("接口文档文件夹不存在或无法访问: %s", f.documentationDir)
}
var foundPath string
var checkedFiles []string
err := filepath.Walk(f.documentationDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
f.logger.Debug("访问文件/目录时出错,跳过",
zap.String("path", path),
zap.Error(err),
)
return nil // 忽略访问错误,继续搜索
}
// 只处理PDF文件
if info.IsDir() || !strings.HasSuffix(strings.ToLower(path), ".pdf") {
return nil
}
// 获取文件名(不包含路径)
fileName := info.Name()
checkedFiles = append(checkedFiles, fileName)
// 转换为小写进行大小写不敏感匹配
fileNameLower := strings.ToLower(fileName)
productCodeLower := strings.ToLower(productCode)
// 方式1: 检查文件名是否以 _{产品代码}.pdf 结尾(大小写不敏感)
suffixPattern := fmt.Sprintf("_%s.pdf", productCodeLower)
if strings.HasSuffix(fileNameLower, suffixPattern) {
foundPath = path
f.logger.Info("找到匹配的PDF文件后缀匹配",
zap.String("product_code", productCode),
zap.String("file_name", fileName),
zap.String("file_path", path),
)
return filepath.SkipAll // 找到后停止搜索
}
// 方式2: 使用filepath.Match进行模式匹配作为备用
matched, matchErr := filepath.Match(searchPattern, fileName)
if matchErr == nil && matched {
foundPath = path
f.logger.Info("找到匹配的PDF文件模式匹配",
zap.String("product_code", productCode),
zap.String("file_name", fileName),
zap.String("file_path", path),
)
return filepath.SkipAll // 找到后停止搜索
}
return nil
})
if err != nil {
f.logger.Error("搜索PDF文件时出错",
zap.String("product_code", productCode),
zap.Error(err),
)
return "", fmt.Errorf("搜索PDF文件时出错: %w", err)
}
if foundPath == "" {
// 查找包含产品编码前缀的类似文件,用于调试
var similarFiles []string
if len(productCode) >= 4 {
productCodePrefix := productCode[:4] // 取前4个字符作为前缀如JRZQ
for _, fileName := range checkedFiles {
fileNameLower := strings.ToLower(fileName)
if strings.Contains(fileNameLower, strings.ToLower(productCodePrefix)) {
similarFiles = append(similarFiles, fileName)
if len(similarFiles) >= 5 {
break // 只显示最多5个类似文件
}
}
}
}
f.logger.Warn("未找到匹配的PDF文件",
zap.String("product_code", productCode),
zap.String("search_pattern", searchPattern),
zap.String("documentation_dir", f.documentationDir),
zap.Int("checked_files_count", len(checkedFiles)),
zap.Strings("similar_files_with_same_prefix", similarFiles),
zap.Strings("sample_files", func() []string {
if len(checkedFiles) > 10 {
return checkedFiles[:10]
}
return checkedFiles
}()),
)
return "", fmt.Errorf("未找到产品代码为 %s 的PDF文档", productCode)
}
// 转换为绝对路径
absPath, err := filepath.Abs(foundPath)
if err != nil {
f.logger.Error("获取文件绝对路径失败",
zap.String("file_path", foundPath),
zap.Error(err),
)
return "", fmt.Errorf("获取文件绝对路径失败: %w", err)
}
f.logger.Info("成功找到PDF文件",
zap.String("product_code", productCode),
zap.String("file_path", absPath),
)
return absPath, nil
}
// FindPDFByProductCodeWithFallback 根据产品代码查找PDF文件支持多个可能的命名格式
func (f *PDFFinder) FindPDFByProductCodeWithFallback(productCode string) (string, error) {
// 尝试多种可能的文件命名格式
patterns := []string{
fmt.Sprintf("*_%s.pdf", productCode), // 标准格式: 产品名称_{代码}.pdf
fmt.Sprintf("%s*.pdf", productCode), // 以代码开头
fmt.Sprintf("*%s*.pdf", productCode), // 包含代码
}
var foundPath string
for _, pattern := range patterns {
err := filepath.Walk(f.documentationDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() || !strings.HasSuffix(strings.ToLower(path), ".pdf") {
return nil
}
fileName := info.Name()
if matched, _ := filepath.Match(pattern, fileName); matched {
foundPath = path
return filepath.SkipAll
}
return nil
})
if err == nil && foundPath != "" {
break
}
}
if foundPath == "" {
return "", fmt.Errorf("未找到产品代码为 %s 的PDF文档", productCode)
}
absPath, err := filepath.Abs(foundPath)
if err != nil {
return "", fmt.Errorf("获取文件绝对路径失败: %w", err)
}
return absPath, nil
}