package service
import (
"fmt"
"log"
"os"
"regexp"
"sort"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// JSONProcessor 处理 JSON 数据的工具类
type JSONProcessor struct {
jsonStr string
}
// NewJSONProcessor 创建新的 JSON 处理器
func NewJSONProcessor(jsonStr string) *JSONProcessor {
return &JSONProcessor{jsonStr: jsonStr}
}
// RemoveInvalidImage 删除不合规图片及其关联数据
func (jp *JSONProcessor) RemoveInvalidImage(invalidURL string) error {
// 1. 检查并删除 spec_detail 中的规格值
if err := jp.removeSpecValuesByImage(invalidURL); err != nil {
return err
}
// 2. 删除其他图片字段中的无效图片
fields := []string{"white_background_pic", "main_image_three_to_four", "pic"}
for _, field := range fields {
if err := jp.removeImageFromField(field, invalidURL); err != nil {
return fmt.Errorf("failed to remove from %s: %v", field, err)
}
}
// 3. 从描述中删除图片
if err := jp.removeImageFromDescription(invalidURL); err != nil {
return fmt.Errorf("failed to remove from description: %v", err)
}
return nil
}
// removeSpecValuesByImage 删除包含指定图片URL的规格值
func (jp *JSONProcessor) removeSpecValuesByImage(invalidURL string) error {
basePath := "schema.model"
specDetailPath := fmt.Sprintf("%s.spec_detail.value", basePath)
result := gjson.Get(jp.jsonStr, specDetailPath)
if !result.Exists() {
return nil // 没有规格数据,直接返回
}
// 收集需要删除的规格值ID
var specValueIDs []string
result.ForEach(func(_, group gjson.Result) bool {
group.Get("spec_values").ForEach(func(_, spec gjson.Result) bool {
if imgURL := spec.Get("img_url").String(); imgURL == invalidURL {
if id := spec.Get("id").String(); id != "" {
specValueIDs = append(specValueIDs, id)
}
}
return true
})
return true
})
// 删除所有关联的规格值
for _, id := range specValueIDs {
if err := jp.RemoveSpecValueByID(id); err != nil {
return fmt.Errorf("failed to remove spec value %s: %v", id, err)
}
}
return nil
}
// removeImageFromField 从指定图片字段中删除无效图片
func (jp *JSONProcessor) removeImageFromField(fieldPath, invalidURL string) error {
fullPath := fmt.Sprintf("schema.model.%s.value", fieldPath)
result := gjson.Get(jp.jsonStr, fullPath)
if !result.Exists() {
return nil // 字段不存在,直接返回
}
// 收集需要删除的索引
var indicesToDelete []int
result.ForEach(func(index gjson.Result, item gjson.Result) bool {
if url := item.Get("url").String(); url == invalidURL {
indicesToDelete = append(indicesToDelete, int(index.Int()))
}
return true
})
// 从后往前删除(避免索引变化)
sort.Sort(sort.Reverse(sort.IntSlice(indicesToDelete)))
for _, idx := range indicesToDelete {
path := fmt.Sprintf("%s.%d", fullPath, idx)
var err error
jp.jsonStr, err = sjson.Delete(jp.jsonStr, path)
if err != nil {
return err
}
}
return nil
}
// unescapeUnicode 解码 Unicode 转义序列(如 \u003c -> <)
func unescapeUnicode(s string) string {
var buf strings.Builder
reader := strings.NewReader(s) // 使用不同的变量名避免冲突
for {
r, _, err := reader.ReadRune() // 读取原始字符
if err != nil {
break
}
if r == '\\' {
next, _, _ := reader.ReadRune() // 读取下一个字符
if next == 'u' {
// 读取 4 位十六进制
hex := make([]rune, 4)
for i := 0; i < 4; i++ {
ch, _, _ := reader.ReadRune()
hex[i] = ch
}
// 转换为 rune
if code, err := strconv.ParseInt(string(hex), 16, 32); err == nil {
buf.WriteRune(rune(code))
continue
}
buf.WriteRune('\\')
buf.WriteRune('u')
buf.WriteString(string(hex))
continue
}
buf.WriteRune('\\')
buf.WriteRune(next)
continue
}
buf.WriteRune(r)
}
return buf.String()
}
// escapeUnicode 将特殊字符编码为 Unicode 转义序列
func escapeUnicode(s string) string {
var buf strings.Builder
for _, r := range s {
if r <= 0x7F { // ASCII 字符
switch r {
case '"', '\\', '<', '>', '&':
buf.WriteString(fmt.Sprintf("\\u%04X", r))
default:
buf.WriteRune(r)
}
} else { // 非 ASCII 字符
if utf8.ValidRune(r) {
buf.WriteRune(r)
} else {
// 处理代理对(surrogate pairs)
for _, surr := range utf16.Encode([]rune{r}) {
buf.WriteString(fmt.Sprintf("\\u%04X", surr))
}
}
}
}
return buf.String()
}
// removeImageFromDescription 从描述中删除无效图片(处理 Unicode 转义)
func (jp *JSONProcessor) removeImageFromDescription(invalidURL string) error {
descPath := "schema.model.description.value"
result := gjson.Get(jp.jsonStr, descPath)
if !result.Exists() {
return nil
}
// 1. 获取并解码 Unicode 转义的 HTML
escapedHTML := result.String()
decodedHTML := unescapeUnicode(escapedHTML)
// 2. 处理图片删除
escapedURL := regexp.QuoteMeta(invalidURL)
re := regexp.MustCompile(
`<img\s[^>]*?src\s*=\s*["']` + escapedURL + `["'][^>]*>`,
)
newHTML := re.ReplaceAllString(decodedHTML, "")
// 3. 重新编码为 Unicode 转义序列
reencodedHTML := escapeUnicode(newHTML)
// 4. 使用 SetRaw 避免二次编码
var err error
jp.jsonStr, err = sjson.SetRaw(jp.jsonStr, descPath, strconv.Quote(reencodedHTML))
return err
}
// ExtractAllPics 提取所有图片 URL(包括描述中的图片)
func (jp *JSONProcessor) ExtractAllPics() []string {
// 基础路径
basePath := "schema.model"
// 存储所有图片的集合(使用 map 去重)
uniquePics := make(map[string]bool)
// 1. 处理指定字段的图片
fields := []string{"spec_detail", "white_background_pic", "main_image_three_to_four", "pic"}
for _, field := range fields {
path := fmt.Sprintf("%s.%s.value", basePath, field)
result := gjson.Get(jp.jsonStr, path)
if result.Exists() {
result.ForEach(func(_, item gjson.Result) bool {
// 处理 spec_detail 的特殊结构
if field == "spec_detail" {
item.Get("spec_values").ForEach(func(_, spec gjson.Result) bool {
if img := spec.Get("img_url"); img.Exists() {
uniquePics[img.String()] = true
}
return true
})
} else if url := item.Get("url"); url.Exists() {
// 处理其他标准字段
uniquePics[url.String()] = true
}
return true
})
}
}
// 2. 处理 description 中的图片
descPath := fmt.Sprintf("%s.description.value", basePath)
descResult := gjson.Get(jp.jsonStr, descPath)
if descResult.Exists() {
html := descResult.String()
re := regexp.MustCompile(`<img[^>]+src="([^">]+)"`)
matches := re.FindAllStringSubmatch(html, -1)
for _, match := range matches {
if len(match) > 1 && !uniquePics[match[1]] {
uniquePics[match[1]] = true
}
}
}
// 转换为切片返回
allPics := make([]string, 0, len(uniquePics))
for pic := range uniquePics {
allPics = append(allPics, pic)
}
return allPics
}
// RemoveSpecValueByID 根据 spec_value ID 删除规格值及其关联的 SKU
func (jp *JSONProcessor) RemoveSpecValueByID(specValueID string) error {
basePath := "schema.model"
// 1. 删除 spec_detail 中的 spec_value
specDetailPath := fmt.Sprintf("%s.spec_detail.value", basePath)
specDetailResult := gjson.Get(jp.jsonStr, specDetailPath)
if !specDetailResult.Exists() {
return fmt.Errorf("spec_detail.value does not exist")
}
specGroups := specDetailResult.Array()
// 收集需要删除的索引(组索引和值索引)
type deletionPoint struct {
groupIndex int
valueIndex int
}
var deletions []deletionPoint
// 遍历所有规格组
for groupIdx, group := range specGroups {
specValues := group.Get("spec_values").Array()
// 遍历组内的规格值
for valueIdx, value := range specValues {
if id := value.Get("id").String(); id == specValueID {
deletions = append(deletions, deletionPoint{groupIdx, valueIdx})
}
}
}
// 从后往前删除(避免索引变化)
sort.Slice(deletions, func(i, j int) bool {
if deletions[i].groupIndex == deletions[j].groupIndex {
return deletions[i].valueIndex > deletions[j].valueIndex
}
return deletions[i].groupIndex > deletions[j].groupIndex
})
for _, del := range deletions {
path := fmt.Sprintf("%s.%d.spec_values.%d", specDetailPath, del.groupIndex, del.valueIndex)
var err error
jp.jsonStr, err = sjson.Delete(jp.jsonStr, path)
if err != nil {
return err
}
}
// 2. 删除关联的 SKU
skuDetailPath := fmt.Sprintf("%s.sku_detail.value", basePath)
skuDetailResult := gjson.Get(jp.jsonStr, skuDetailPath)
if !skuDetailResult.Exists() {
// 没有 SKU 数据,直接返回
return nil
}
skus := skuDetailResult.Array()
var skuIndicesToDelete []int
// 收集需要删除的 SKU 索引
for idx, sku := range skus {
specDetailIDs := sku.Get("spec_detail_ids").Array()
for _, id := range specDetailIDs {
if id.String() == specValueID {
skuIndicesToDelete = append(skuIndicesToDelete, idx)
break // 找到匹配即跳出
}
}
}
// 从后往前删除 SKU(避免索引变化)
sort.Sort(sort.Reverse(sort.IntSlice(skuIndicesToDelete)))
for _, idx := range skuIndicesToDelete {
path := fmt.Sprintf("%s.%d", skuDetailPath, idx)
var err error
jp.jsonStr, err = sjson.Delete(jp.jsonStr, path)
if err != nil {
return err
}
}
return nil
}
// UpdateSkuPrice 更新 SKU 的价格
func (jp *JSONProcessor) UpdateSkuPrice(skuID, newPrice string) error {
path := "schema.model.sku_detail.value"
result := gjson.Get(jp.jsonStr, path)
if !result.Exists() {
return fmt.Errorf("sku_detail.value does not exist")
}
skuArray := result.Array()
for idx, sku := range skuArray {
if id := sku.Get("id").String(); id == skuID {
fullPath := fmt.Sprintf("%s.%d.price", path, idx)
var err error
jp.jsonStr, err = sjson.Set(jp.jsonStr, fullPath, newPrice)
return err
}
}
return fmt.Errorf("SKU with ID %s not found", skuID)
}
// UpdateSkuStock 更新 SKU 的库存
func (jp *JSONProcessor) UpdateSkuStock(skuID string, newStock int) error {
path := "schema.model.sku_detail.value"
result := gjson.Get(jp.jsonStr, path)
if !result.Exists() {
return fmt.Errorf("sku_detail.value does not exist")
}
skuArray := result.Array()
for idx, sku := range skuArray {
if id := sku.Get("id").String(); id == skuID {
// 更新 stock_info.stock_num
stockPath := fmt.Sprintf("%s.%d.stock_info.stock_num", path, idx)
var err error
jp.jsonStr, err = sjson.Set(jp.jsonStr, stockPath, newStock)
if err != nil {
return err
}
// 更新 self_sell_stock
selfStockPath := fmt.Sprintf("%s.%d.self_sell_stock", path, idx)
jp.jsonStr, err = sjson.Set(jp.jsonStr, selfStockPath, newStock)
return err
}
}
return fmt.Errorf("SKU with ID %s not found", skuID)
}
// GetField 获取指定字段的值
func (jp *JSONProcessor) GetField(fieldPath string) (gjson.Result, bool) {
fullPath := fmt.Sprintf("schema.model.%s", fieldPath)
result := gjson.Get(jp.jsonStr, fullPath)
return result, result.Exists()
}
// UpdateField 更新指定字段的值
func (jp *JSONProcessor) UpdateField(fieldPath string, newValue interface{}) error {
fullPath := fmt.Sprintf("schema.model.%s", fieldPath)
newJSON, err := sjson.Set(jp.jsonStr, fullPath, newValue)
if err != nil {
return err
}
jp.jsonStr = newJSON
return nil
}
// DeleteField 删除指定字段
func (jp *JSONProcessor) DeleteField(fieldPath string) error {
fullPath := fmt.Sprintf("schema.model.%s", fieldPath)
newJSON, err := sjson.Delete(jp.jsonStr, fullPath)
if err != nil {
return err
}
jp.jsonStr = newJSON
return nil
}
// AddArrayItem 向数组字段添加新元素
func (jp *JSONProcessor) AddArrayItem(fieldPath string, newItem interface{}) error {
// 检查数组字段是否存在
basePath := fmt.Sprintf("schema.model.%s.value", fieldPath)
result := gjson.Get(jp.jsonStr, basePath)
// 如果数组不存在,先创建空数组
if !result.Exists() {
var err error
jp.jsonStr, err = sjson.Set(jp.jsonStr, basePath, []interface{}{})
if err != nil {
return fmt.Errorf("failed to create array field: %v", err)
}
}
// 添加新元素到数组末尾
fullPath := fmt.Sprintf("%s.-1", basePath)
newJSON, err := sjson.Set(jp.jsonStr, fullPath, newItem)
if err != nil {
return fmt.Errorf("failed to add array item: %v", err)
}
jp.jsonStr = newJSON
return nil
}
// RemoveArrayItem 从数组中删除指定索引的元素
func (jp *JSONProcessor) RemoveArrayItem(fieldPath string, index int) error {
basePath := fmt.Sprintf("schema.model.%s.value", fieldPath)
result := gjson.Get(jp.jsonStr, basePath)
if !result.Exists() {
return fmt.Errorf("array field does not exist")
}
// 检查索引是否有效
array := result.Array()
if index < 0 || index >= len(array) {
return fmt.Errorf("index %d out of range [0, %d]", index, len(array)-1)
}
fullPath := fmt.Sprintf("%s.%d", basePath, index)
newJSON, err := sjson.Delete(jp.jsonStr, fullPath)
if err != nil {
return err
}
jp.jsonStr = newJSON
return nil
}
// GetJSON 获取当前处理后的 JSON
func (jp *JSONProcessor) GetJSON() string {
return jp.jsonStr
}
// TestFunc 测试函数
func TestFunc() {
content, err := os.ReadFile("asyncCheckPost.json")
if err != nil {
log.Printf("读取过滤文件失败: %v", err)
return
}
processor := NewJSONProcessor(string(content))
// 提取所有图片
allPics := processor.ExtractAllPics()
fmt.Printf("提取到 %d 张图片\n", len(allPics))
// 假设我们检测到第二张图片不合规
if len(allPics) > 1 {
invalidURL := "https://p3-aio.ecombdimg.com/obj/ecom-shop-material/jpeg_m_a469f301b9c195b6b833240f9adc1ffc_sx_36996_www790-314"
fmt.Printf("检测到不合规图片: %s\n", invalidURL)
// 删除不合规图片及其关联数据
if err := processor.RemoveInvalidImage(invalidURL); err != nil {
fmt.Println("删除不合规图片失败:", err)
} else {
fmt.Println("成功删除不合规图片及其关联数据")
// 验证删除后图片数量
newPics := processor.ExtractAllPics()
fmt.Printf("删除后剩余图片: %d 张\n", len(newPics))
// 检查是否不再包含不合规图片
for _, url := range newPics {
if url == invalidURL {
fmt.Println("错误: 不合规图片仍然存在!")
break
}
}
}
}
// 1. 删除规格值及其关联的 SKU
specValueID := "1839581026529404" // 小号钢架尺30米的 ID
if err := processor.RemoveSpecValueByID(specValueID); err != nil {
fmt.Println("删除规格值失败:", err)
} else {
fmt.Println("成功删除规格值及其关联SKU")
}
// 2. 更新 SKU 价格和库存
skuID := "3527608283404802" // 第一个 SKU 的 ID
newPrice := "99.99"
newStock := 3000
if err := processor.UpdateSkuPrice(skuID, newPrice); err != nil {
fmt.Println("更新价格失败:", err)
} else {
fmt.Println("成功更新SKU价格")
}
if err := processor.UpdateSkuStock(skuID, newStock); err != nil {
fmt.Println("更新库存失败:", err)
} else {
fmt.Println("成功更新SKU库存")
}
// 3. 测试数组操作
// 向不存在的数组添加元素
if err := processor.AddArrayItem("new_field", map[string]string{"test": "value"}); err != nil {
fmt.Println("添加数组项失败:", err)
} else {
fmt.Println("成功添加数组项到新字段")
}
// 尝试删除不存在的数组项
if err := processor.RemoveArrayItem("non_existent_field", 0); err != nil {
fmt.Println("删除数组项失败(预期):", err)
} else {
fmt.Println("意外成功删除不存在的数组项")
}
// 尝试越界删除
if err := processor.RemoveArrayItem("pic", 100); err != nil {
fmt.Println("删除数组项失败(预期):", err)
} else {
fmt.Println("意外成功删除越界数组项")
}
// 获取最终 JSON
// finalJSON := processor.GetJSON()
// fmt.Println(finalJSON)
}
这段代码中,请帮我重构一下,因为schema.model 的路径有可能为 data.model,需要根据json数据来判断。