如果单个 XML 文件符合要求,如何创建多个 XML 文件?

How to create multiple XML files if single XML file matches the requirements?

下面是我的客户端代码,它从 golang grpc 服务器流式传输所有客户 url 并且工作正常。它采用 Request 输入参数并在特定 clientId 的基础上流式传输 customer url's。在我下面的代码中,我正在为 ClientId 12345 流式传输所有客户 url 并且它工作正常。

我还创建了一个 XML 文件,其中包含所有 URL,特别是 clientId,如下所示。例如:下面将创建 12345_abc.xml XML 文件,其中包含特定格式的所有 URL。

func main() {
    // this "clientId" will be configurable in future
    clientId := 12345
    timeout := time.Duration(1000) * time.Millisecond
    ctx, _ := context.WithTimeout(context.Background(), timeout)
    conn, err := grpc.DialContext(ctx, "localhost:50005", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("can not connect with server %v", err)
    }

    // create stream
    client := pb.NewCustomerServiceClient(conn)
    req := &pb.Request{ClientId: clientId}
    stream, err := client.FetchResponse(context.Background(), req)
    if err != nil {
        log.Fatalf("open stream error %v", err)
    }
    // create new object to populate all URL data in memory
    urlHolder := NewClient()
    t := time.Unix(0, 0).UTC()
    done := make(chan bool)
    go func() {
        for {
            resp, err := stream.Recv()
            if err == io.EOF {
                done <- true
                return
            }
            if err != nil {
                log.Fatalf("can not receive %v", err)
            }
            log.Printf("Resp received: %s", resp.GetCustomerUrl())
            // populate URL object with all the required field in it
            urlHolder.Add(&URL{
               Loc:        resp.GetCustomerUrl(),
               LastMod:    &t,
               ChangeFreq: Daily,
               Priority:   10.2,
            })
        }
    }()

    <-done
    log.Printf("finished")
    // create an XML file with all the URL's in it and then save it on disk
    // for particular clientId. This will create "12345_abc.xml"
    file, _ := os.Create(fmt.Sprintf("%d_abc.xml", clientId))
    urlHolder.WriteTo(file)
}

这是我的 urlholder.go 文件:

type URL struct {
    Loc        string     `xml:"loc"`
    LastMod    *time.Time `xml:"lastmod"`
    ChangeFreq ChangeFreq `xml:"changefreq"`
    Priority   float32    `xml:"priority"`
}

type UrlMap struct {
    XMLName xml.Name `xml:"urlset"`
    Xmlns   string   `xml:"xmlns,attr"`
    URLs    []*URL   `xml:"url"`
    Minify  bool     `xml:"-"`
}

func NewClient() *UrlMap {
    return &UrlMap{
        Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
        URLs:  make([]*URL, 0),
    }
}

func (s *UrlMap) Add(u *URL) {
    s.URLs = append(s.URLs, u)
}

// WriteTo writes XML encoded urlMap to given io.Writer.
func (s *UrlMap) WriteTo(w io.Writer) (n int64, err error) {
    cw := NewCounterWriter(w)
    _, err = cw.Write([]byte(xml.Header))
    if err != nil {
      return cw.Count(), err
    }
    en := xml.NewEncoder(cw)
    if !s.Minify {
      en.Indent("", "  ")
    }
    err = en.Encode(s)
    cw.Write([]byte{'\n'})
    return cw.Count(), err
}

这是我的 CounterWriter class -

// CounterWriter implements io.Writer. Count of bytes written is tracked.
type CounterWriter struct {
    writer io.Writer
    count  int64
}

var _ io.Writer = (*CounterWriter)(nil)

// NewCounterWriter wraps io.Writer and returns CounterWriter.
func NewCounterWriter(w io.Writer) (cw *CounterWriter) {
    return &CounterWriter{
        writer: w,
    }
}

// Write calls Write on the wrapped io.Writer and adds the number of bytes
// written to the counter.
func (cw *CounterWriter) Write(p []byte) (n int, err error) {
    n, err = cw.writer.Write(p)
    cw.count = cw.count + int64(n)
    return n, err
}

// Count returns the number of bytes written to the Writer.
func (cw *CounterWriter) Count() (n int64) {
    return cw.count
}

问题陈述

上面的代码工作正常,但我需要将一个 XML 文件拆分为多个 XML 文件以获得相同的 clientId 如果它符合以下要求:

我知道 50k URL 限制会比 50MB 限制更快达到,但这就是我的要求。现在根据以上逻辑,我需要为特定的 clientId 创建多个 XML 文件。所有这些多个文件都可以像这样 12345_abc_1.xml12345_abc_2.xml 或任何其他更好的命名格式。我有点困惑我应该如何继续这样做。

我可以通过使用 for 循环为 50K url 添加逻辑,但混淆了大小逻辑,而且我想为每个 clientId 制作这个通用的,所以我遇到了困难这样做。

在你的 WriteTo 函数中,你应该调用类似 w.Write(myBytes).

的东西

该函数中 myBytes 的大小就是您要查找的大小。您可以使用 len(myBytes)w.Write(myBytes) 的第一个 return 来获取它。这很重要,因为除了直接计算您将写入的信息外,无法“估计”文件的大小。

您正在将 UrlMap 转换为 WriteTo 函数中某处的字节。这意味着您可以对任何 URL 变量执行相同的操作。

我解决这个问题的方法是有一个 sizeCounter 并添加每次我在 for { 中创建一个新的 URL 变量时将存储的字节数环形。在同一个地方,我还会计算创建的 URL 的数量。有了两个计数器,剩下的就很简单了。

我会在 .Add 函数和 return 函数中添加从 URLbytes 的转换,这样一切都更容易理解。您将不得不将一些变量移动到 go 例程中。


func (s *UrlMap) Add(u *URL) (int) { // Modify this function to count the size and return it
    s.URLs = append(s.URLs, u)

    var urlBytes []byte
    var err error

    urlBytes, err = xml.Marshal(u) // Transform to bytes using xml.Marshal or xml.MarshalIndent
    if err != nil {
        panic(err) // or return the error if you want
    }

    return len(urlBytes)
}
    t := time.Unix(0, 0).UTC()
    done := make(chan bool)
    go func() {
        // create new object to populate all URL data in memory
        urlHolder := NewClient()
        urlCounter := 0
        byteCounter := 0
        fileCounter := 0

        for {
            resp, err := stream.Recv()
            if err == io.EOF {
                done <- true
                file, _ := os.Create(fmt.Sprintf("%d_abc_%d.xml", clientId, fileCounter))
                urlHolder.WriteTo(file)
                return
            }
            if err != nil {
                log.Fatalf("can not receive %v", err)
            }
            log.Printf("Resp received: %s", resp.GetCustomerUrl())
            // I add the bytes of the URL here as a return
            urlBytes := urlHolder.Add(&URL{
               Loc:        resp.GetCustomerUrl(),
               LastMod:    &t,
               ChangeFreq: Daily,
               Priority:   10.2,
            })
            byteCounter += urlBytes
            urlCounter += 1
            if byteCounter > 49000000 || urlCounter >= 50000 { 
                file, _ := os.Create(fmt.Sprintf("%d_abc_%d.xml", clientId, fileCounter))
                urlHolder.WriteTo(file)
                urlHolder = NewClient() // create a new object for next loop
                fileCounter += 1 // prepare fileCounter for next loop
                byteCounter = 0 // restart count variables
                urlCounter = 0
            }
        }
    }()

    <-done
    log.Printf("finished")
    // No longer write the files here.