243 lines
6.9 KiB
Go
243 lines
6.9 KiB
Go
|
|
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
|
|||
|
|
}
|