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 }