package controller import ( "encoding/json" "fmt" "log" "strconv" "sync" "time" _type "xianyv/type" "xianyv/utils/checkUtil" "xianyv/utils/creatGoodsUtil" "github.com/xuri/excelize/v2" ) type GoodsController struct { ExcelPath string TxtPath string SheetName string } // 简化缓存结构,避免复杂锁嵌套 type FileCache struct { categoryMap map[string]string categoryTime time.Time categoryMutex sync.RWMutex // Excel数据内存缓存,key为sheetName,value为map[col][int32]bool excelCache map[string]map[string]map[int32]bool excelCacheTime map[string]time.Time excelCacheMutex sync.RWMutex excelCacheDuration time.Duration // Excel操作全局互斥锁(兼容老逻辑,后续可移除) excelMutex sync.Mutex cacheDuration time.Duration } var ( fileCacheInstance *FileCache once sync.Once ) func GetFileCache() *FileCache { once.Do(func() { fileCacheInstance = &FileCache{ cacheDuration: 5 * time.Minute, excelCache: make(map[string]map[string]map[int32]bool), excelCacheTime: make(map[string]time.Time), excelCacheDuration: 5 * time.Minute, } }) return fileCacheInstance } // 预加载Excel校验数据到内存 func (fc *FileCache) PreloadExcelCache(filename, sheetName string, columns []string) error { fc.excelCacheMutex.Lock() defer fc.excelCacheMutex.Unlock() // 只加载指定sheet和列 cache := make(map[string]map[int32]bool) // col -> set of int32 f, err := excelize.OpenFile(filename) if err != nil { return err } defer f.Close() rows, err := f.GetRows(sheetName) if err != nil { return err } for _, col := range columns { cache[col] = make(map[int32]bool) } for rowNum := 1; rowNum <= len(rows); rowNum++ { for _, col := range columns { cellAddress := fmt.Sprintf("%s%d", col, rowNum) cellValue, err := f.GetCellValue(sheetName, cellAddress) if err != nil || cellValue == "" { continue } cellInt, err := strconv.ParseInt(cellValue, 10, 32) if err != nil { continue } cache[col][int32(cellInt)] = true } } if fc.excelCache == nil { fc.excelCache = make(map[string]map[string]map[int32]bool) } fc.excelCache[sheetName] = cache if fc.excelCacheTime == nil { fc.excelCacheTime = make(map[string]time.Time) } fc.excelCacheTime[sheetName] = time.Now() return nil } // GetCategoryMap 获取类目映射(简化版本) func (fc *FileCache) GetCategoryMap(txtPath string) (map[string]string, error) { fc.categoryMutex.RLock() if fc.categoryMap != nil && time.Since(fc.categoryTime) < fc.cacheDuration { defer fc.categoryMutex.RUnlock() return fc.categoryMap, nil } fc.categoryMutex.RUnlock() fc.categoryMutex.Lock() defer fc.categoryMutex.Unlock() // 双重检查 if fc.categoryMap != nil && time.Since(fc.categoryTime) < fc.cacheDuration { return fc.categoryMap, nil } log.Printf("更新类目缓存...") categoryMap, err := checkUtil.CheckValuesInTxt(txtPath) if err != nil { return nil, err } fc.categoryMap = categoryMap fc.categoryTime = time.Now() log.Printf("类目缓存更新完成,共 %d 个类目", len(categoryMap)) return categoryMap, nil } // CheckValuesInExcelWithMutex 优先用内存缓存校验,若无则自动预加载 func (fc *FileCache) CheckValuesInExcelWithMutex(filename, sheetName string, values map[string]int32) (map[string]bool, error) { // 先尝试用内存缓存 fc.excelCacheMutex.RLock() cache, ok := fc.excelCache[sheetName] fc.excelCacheMutex.RUnlock() columns := make([]string, 0, len(values)) for col := range values { columns = append(columns, col) } if !ok { // 缓存不存在,自动预加载 if err := fc.PreloadExcelCache(filename, sheetName, columns); err != nil { return nil, err } fc.excelCacheMutex.RLock() cache, ok = fc.excelCache[sheetName] fc.excelCacheMutex.RUnlock() if !ok { return nil, fmt.Errorf("Excel缓存加载失败: %s", sheetName) } } // 校验 result := make(map[string]bool) for col, target := range values { if colMap, ok := cache[col]; ok { result[col] = colMap[target] } else { result[col] = false } } return result, nil } // createErrorResponse 创建错误响应 func createErrorResponse(format string, args ...interface{}) ([]byte, error) { resp := _type.ErrResponse{ Code: 0, Msg: fmt.Sprintf(format, args...), Data: struct{}{}, } response, err := json.Marshal(resp) return response, err } // GoodsCreatController 修复死锁问题的版本 func (c *GoodsController) GoodsCreatController(body _type.Body, batchCreatRequest, domain string, flag bool) ([]byte, error) { startTime := time.Now() defer func() { log.Printf("GoodsCreatController 执行时间: %v", time.Since(startTime)) }() fileCache := GetFileCache() // 类目ID基础校验 catStart := time.Now() categoryMap, err := fileCache.GetCategoryMap(c.TxtPath) log.Printf("GetCategoryMap elapsed=%v", time.Since(catStart)) if err != nil { log.Printf("读取文件失败: %v", err) return createErrorResponse("读取文件失败: %v", err) } if _, exists := categoryMap[body.CatIds]; !exists { log.Printf("错误的类目ID: %s", body.CatIds) return createErrorResponse("错误的类目ID: %s", body.CatIds) } // 省市区基础校验 valuesToCheck := map[string]int32{ "A": body.Province, "C": body.City, "E": body.District, } checkStart := time.Now() checkResults, err := fileCache.CheckValuesInExcelWithMutex(c.ExcelPath, c.SheetName, valuesToCheck) log.Printf("CheckValuesInExcelWithMutex elapsed=%v", time.Since(checkStart)) if err != nil { log.Printf("Excel检查失败: %v", err) } else { log.Printf("检查结果: %v", checkResults) } // 业务逻辑处理 switch body.TypePlatform { case 4: createStart := time.Now() createResponse, err := creatGoodsUtil.XianYvCreat(body, batchCreatRequest, body.AppId, body.AppSecret, domain, flag) log.Printf("XianYvCreat elapsed=%v", time.Since(createStart)) if err != nil { log.Printf("创建商品失败: %v", err) return createErrorResponse("创建商品失败: %v", err) } return createResponse, nil default: return createErrorResponse("平台编号有误: %d", body.TypePlatform) } } // GoodsCreatController 分离BookData的全局变量 func (c *GoodsController) GoodsCreatControllerNew(body _type.BodyNew, batchCreatRequest, domain string, flag bool) ([]byte, error) { startTime := time.Now() defer func() { log.Printf("GoodsCreatController 执行时间: %v", time.Since(startTime)) }() fileCache := GetFileCache() // 类目ID基础校验 catStart := time.Now() categoryMap, err := fileCache.GetCategoryMap(c.TxtPath) log.Printf("GetCategoryMap elapsed=%v", time.Since(catStart)) if err != nil { log.Printf("读取文件失败: %v", err) return createErrorResponse("读取文件失败: %v", err) } if _, exists := categoryMap[body.CatIds]; !exists { log.Printf("错误的类目ID: %s", body.CatIds) return createErrorResponse("错误的类目ID: %s", body.CatIds) } // 省市区基础校验 valuesToCheck := map[string]int32{ "A": body.Province, "C": body.City, "E": body.District, } checkStart := time.Now() checkResults, err := fileCache.CheckValuesInExcelWithMutex(c.ExcelPath, c.SheetName, valuesToCheck) log.Printf("CheckValuesInExcelWithMutex elapsed=%v", time.Since(checkStart)) if err != nil { log.Printf("Excel检查失败: %v", err) } else { log.Printf("检查结果: %v", checkResults) } // 业务逻辑处理 switch body.TypePlatform { case 4: createStart := time.Now() if len(body.BookData) > 0 { createResponse, err := creatGoodsUtil.XianYvCreatNew(body, batchCreatRequest, body.AppId, body.AppSecret, domain, flag) log.Printf("XianYvCreat elapsed=%v", time.Since(createStart)) if err != nil { log.Printf("创建商品失败: %v", err) return createErrorResponse("创建商品失败: %v", err) } return createResponse, nil } else { createResponse, err := creatGoodsUtil.XianYvCreatNoIsbn(body, batchCreatRequest, body.AppId, body.AppSecret, domain, flag) log.Printf("XianYvCreat elapsed=%v", time.Since(createStart)) if err != nil { log.Printf("创建商品失败: %v", err) return createErrorResponse("创建商品失败: %v", err) } return createResponse, nil } default: return createErrorResponse("平台编号有误: %d", body.TypePlatform) } } // 刷新指定sheet的Excel缓存(重新加载) func (fc *FileCache) RefreshExcelCache(filename, sheetName string, columns []string) error { return fc.PreloadExcelCache(filename, sheetName, columns) } // 清空所有Excel缓存(下次校验时会自动重新加载) func (fc *FileCache) ClearAllExcelCache() { fc.excelCacheMutex.Lock() defer fc.excelCacheMutex.Unlock() fc.excelCache = make(map[string]map[string]map[int32]bool) fc.excelCacheTime = make(map[string]time.Time) } // 直接在内存缓存校验Excel值 // values: map[col]目标值 // 返回: map[col]是否存在 func (fc *FileCache) CheckValuesInExcelCache(sheetName string, values map[string]int32) (map[string]bool, error) { fc.excelCacheMutex.RLock() defer fc.excelCacheMutex.RUnlock() result := make(map[string]bool) cache, ok := fc.excelCache[sheetName] if !ok { return nil, fmt.Errorf("Excel缓存未加载: %s", sheetName) } for col, target := range values { if colMap, ok := cache[col]; ok { result[col] = colMap[target] } else { result[col] = false } } return result, nil }