Golang中文件内容原始结构与REST API端点返回结果不匹配的问题

Golang中文件内容原始结构与REST API端点返回结果不匹配的问题 技术栈:HyperledgerFabric 1.4,项目仓库 - FabricSamples 下的 FirstNetwork Go – v1.15,Goa – v3.2.5,Postman

需求:用户将从前端输入 channelName 和 ChannelProfile。我使用 Postman 来触发 REST API 端点。根据业务逻辑,我需要从 Go 触发一个 .sh 文件,该文件将生成一个二进制文件。我需要将这个二进制文件作为响应发送回去。我使用了 ioutil.ReadFile 来读取文件。由于该文件的输出是字节数组,我将其类型转换为字符串。将内容打印到控制台并检查,文件内容显示正确。 以下是文件内容的截图。

screen1: The filecontent from console.

Postman 中的输出:

screen2: The output from postman

我使用了 strings.ToValidUTF8() 和 unicode.IsPrint() 函数来移除无效的字节序列和其他不可打印字符。

现在我得到的输出如下屏幕所示:

screen3:The output from postman after removing invalid Byte Sequence

我期望的是获得与屏幕1中相同的文件结构。 请注意,键 “FileContent” 的值显示了文件的内容。 我对 Go 和 Goa 还比较陌生。请帮助我理解我哪里做错了。任何帮助都将不胜感激。

// design.go 文件,其中定义了 API

package design

import (
    "fmt"
    . "goa.design/goa/v3/dsl"
)

var _ = API("fabric", func() {
    Title("An api for fabric")
    Description("A simple goa service")
    Server("fabric", func() {
        Host("localhost", func() {
            URI("http://localhost:8000")
        })
    })
})

var _ = Service("fabric", func() {
    Description("The service creates profile in configtx.yaml file ")
    //Method to add a new Channel Profile
    Method("create", func() {
        Payload(func() {
            Field(1, "channelName", String, "Name of the channel")
            Field(2, "channelProfile", String, "Name of the channel profile")
            Required("channelName", "channelProfile")
        })
    
        Result(String)
        
        Error("not_found", ErrorResult, "channelProfile not found")
        HTTP(func() {
            POST("/createChannelProfile")               
            Response(StatusCreated)
        })
    })
    Files("/openapi.json", "./gen/http/openapi.json")
})
// fabric.go 文件,其中实现了业务逻辑

package fabricapi

import (
    fabric "GoApp2/gen/fabric"
    "bytes"
    "context"
    "encoding/json"
    "io/ioutil"
    "log"
    "os/exec"
    "strings"
    "unicode"
)

// fabric service example implementation.
// The example methods log the requests and return zero values.
type fabricsrvc struct {
    logger *log.Logger
}

// NewFabric returns the fabric service implementation.
func NewFabric(logger *log.Logger) fabric.Service {
    return &fabricsrvc{logger}
}


type FinalOutput struct {
    Status      string
    FileContent string
}

// Create implements create.
func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (res string, err error) {
    s.logger.Print("fabric.create")
    s.logger.Print("fabric.create")
    cmd := exec.Command("/bin/bash", "../generateChannel.sh", p.ChannelName, p.ChannelProfile)
    stdout, stderr := cmd.Output()
    if stderr != nil {
        s.logger.Print("error %s", err)
    }
    output := string(stdout)
    s.logger.Print(output)

    content, err := ioutil.ReadFile("../channel-artifacts/" + p.ChannelName + ".tx")
    if err != nil {
        s.logger.Print("error %s", err)
    }

    filecontent := string(content)
     s.logger.Print(filecontent)
    removeInvalidByteSequence := strings.ToValidUTF8(filecontent, " ")
        
    clean := strings.Map(func(r rune) rune {
        if unicode.IsPrint(r) {
            return r
        }
        return -1
    }, removeInvalidByteSequence)

    resfile := clean
    s.logger.Print(resfile)

    buf := new(bytes.Buffer)
    // create JSON encoder for `buf`
    bufEncoder := json.NewEncoder(buf)
    if output == "success\n" {
        // encode JSON from `FinalOutput` structs
        bufEncoder.Encode(FinalOutput{"success", resfile})
        res = buf.String()
        s.logger.Print(res)
        return res, nil
    } else {
        bufEncoder.Encode(FinalOutput{"failed", "File not Found : Please input the correct channelProfile"})
        res = buf.String()
        s.logger.Print(res)
        return res, nil
    }
    return
}

更多关于Golang中文件内容原始结构与REST API端点返回结果不匹配的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

你正在移除不可打印和无效编码的序列,却期望文件看起来和之前一样?抱歉,我不太理解这个需求……

更多关于Golang中文件内容原始结构与REST API端点返回结果不匹配的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


首先,你需要了解原始文件的格式,并且需要能够访问该格式的规范或文档,然后重新实现一个解析器,或者寻找一个实现了该功能的库。

是的,Nobbz说得对,这正是我目前遇到的困难,我需要帮助来了解如何实现。任何小小的帮助或提示都将不胜感激。

原始文件是二进制格式,文件扩展名为“.tx”。我已在此处看到您的回复:如何读写二进制文件?

你好 Nobbz,

如果你观察屏幕1,所有可读字符串都是通过空格和换行垂直对齐的。但在屏幕3中,发送响应后,它们是水平对齐的。我希望屏幕3中的间距和对齐方式与屏幕1中看到的保持一致。

通常我不关心屏幕1,因为你在向终端打印随机的二进制数据,它如何被打印/显示是你的Shell和终端模拟器的实现细节。

如果你需要从文件中提取特定的结构,请正确解析它并提取必要的数据。

TX 似乎是一种被许多不同程序使用的文件扩展名,因此仅凭这一点不足以了解其格式。

如果你没有该格式的规范,那么除了猜测之外别无他法,或者用更现代的说法,只能依靠启发式方法。

这种格式甚至可能是专有的,并未公开。

我在Node.js中尝试了同样的操作,它发送的是完全原始的文件,我没有移除无效的字节序列。但在这里,我需要编写步骤来移除无效的字节序列和不可打印字符。这就是我所期望的结果。

correctOutput

问题在于你使用了json.NewEncoderEncode方法,它会自动添加换行符,并且你的响应处理方式不正确。根据你的Goa设计,Result(String)应该返回纯字符串,但你却返回了JSON字符串。

以下是修正后的实现:

// fabric.go - 修正版本
package fabricapi

import (
    fabric "GoApp2/gen/fabric"
    "context"
    "encoding/json"
    "io/ioutil"
    "log"
    "os/exec"
)

type fabricsrvc struct {
    logger *log.Logger
}

func NewFabric(logger *log.Logger) fabric.Service {
    return &fabricsrvc{logger}
}

type FinalOutput struct {
    Status      string `json:"status"`
    FileContent string `json:"fileContent"`
}

func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (res string, err error) {
    s.logger.Print("fabric.create")
    
    cmd := exec.Command("/bin/bash", "../generateChannel.sh", p.ChannelName, p.ChannelProfile)
    stdout, stderr := cmd.Output()
    if stderr != nil {
        s.logger.Printf("command error: %v", stderr)
    }
    
    output := string(stdout)
    s.logger.Print("Script output:", output)

    content, err := ioutil.ReadFile("../channel-artifacts/" + p.ChannelName + ".tx")
    if err != nil {
        s.logger.Printf("file read error: %v", err)
        finalOutput := FinalOutput{
            Status:      "failed",
            FileContent: "File not Found: " + err.Error(),
        }
        jsonBytes, _ := json.Marshal(finalOutput)
        return string(jsonBytes), nil
    }

    filecontent := string(content)
    s.logger.Print("Original file content:", filecontent)

    var finalOutput FinalOutput
    if output == "success\n" {
        finalOutput = FinalOutput{
            Status:      "success",
            FileContent: filecontent,
        }
    } else {
        finalOutput = FinalOutput{
            Status:      "failed",
            FileContent: "File not Found: Please input the correct channelProfile",
        }
    }

    jsonBytes, err := json.Marshal(finalOutput)
    if err != nil {
        s.logger.Printf("JSON marshal error: %v", err)
        return "", err
    }

    res = string(jsonBytes)
    s.logger.Print("Final response:", res)
    return res, nil
}

如果你希望保持原始二进制数据的完整性,应该直接返回字节数组而不是字符串。首先需要修改design.go:

// design.go - 修改Result类型
var _ = Service("fabric", func() {
    Description("The service creates profile in configtx.yaml file ")
    
    Method("create", func() {
        Payload(func() {
            Field(1, "channelName", String, "Name of the channel")
            Field(2, "channelProfile", String, "Name of the channel profile")
            Required("channelName", "channelProfile")
        })
    
        Result(Bytes)  // 改为Bytes类型
        
        Error("not_found", ErrorResult, "channelProfile not found")
        HTTP(func() {
            POST("/createChannelProfile")               
            Response(StatusCreated)
        })
    })
    
    Files("/openapi.json", "./gen/http/openapi.json")
})

然后修改fabric.go:

// fabric.go - 返回二进制数据版本
func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (res []byte, err error) {
    s.logger.Print("fabric.create")
    
    cmd := exec.Command("/bin/bash", "../generateChannel.sh", p.ChannelName, p.ChannelProfile)
    stdout, stderr := cmd.Output()
    if stderr != nil {
        s.logger.Printf("command error: %v", stderr)
    }
    
    output := string(stdout)
    s.logger.Print("Script output:", output)

    content, err := ioutil.ReadFile("../channel-artifacts/" + p.ChannelName + ".tx")
    if err != nil {
        s.logger.Printf("file read error: %v", err)
        errorResponse := map[string]string{
            "status":      "failed",
            "fileContent": "File not Found: " + err.Error(),
        }
        return json.Marshal(errorResponse)
    }

    if output == "success\n" {
        // 直接返回二进制文件内容
        return content, nil
    }
    
    errorResponse := map[string]string{
        "status":      "failed",
        "fileContent": "File not Found: Please input the correct channelProfile",
    }
    return json.Marshal(errorResponse)
}

关键点:

  1. 避免不必要的字符串转换和字符过滤,这会破坏二进制数据的完整性
  2. 使用json.Marshal而不是json.NewEncoder来避免自动添加的换行符
  3. 根据实际需求选择合适的返回类型(String或Bytes)
  4. 保持二进制数据的原始格式,避免UTF-8转换
回到顶部