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 来读取文件。由于该文件的输出是字节数组,我将其类型转换为字符串。将内容打印到控制台并检查,文件内容显示正确。 以下是文件内容的截图。

Postman 中的输出:

我使用了 strings.ToValidUTF8() 和 unicode.IsPrint() 函数来移除无效的字节序列和其他不可打印字符。
现在我得到的输出如下屏幕所示:

我期望的是获得与屏幕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
你正在移除不可打印和无效编码的序列,却期望文件看起来和之前一样?抱歉,我不太理解这个需求……
更多关于Golang中文件内容原始结构与REST API端点返回结果不匹配的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
首先,你需要了解原始文件的格式,并且需要能够访问该格式的规范或文档,然后重新实现一个解析器,或者寻找一个实现了该功能的库。
是的,Nobbz说得对,这正是我目前遇到的困难,我需要帮助来了解如何实现。任何小小的帮助或提示都将不胜感激。
原始文件是二进制格式,文件扩展名为“.tx”。我已在此处看到您的回复:如何读写二进制文件?。
你好 Nobbz,
如果你观察屏幕1,所有可读字符串都是通过空格和换行垂直对齐的。但在屏幕3中,发送响应后,它们是水平对齐的。我希望屏幕3中的间距和对齐方式与屏幕1中看到的保持一致。
通常我不关心屏幕1,因为你在向终端打印随机的二进制数据,它如何被打印/显示是你的Shell和终端模拟器的实现细节。
如果你需要从文件中提取特定的结构,请正确解析它并提取必要的数据。
我在Node.js中尝试了同样的操作,它发送的是完全原始的文件,我没有移除无效的字节序列。但在这里,我需要编写步骤来移除无效的字节序列和不可打印字符。这就是我所期望的结果。

问题在于你使用了json.NewEncoder的Encode方法,它会自动添加换行符,并且你的响应处理方式不正确。根据你的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)
}
关键点:
- 避免不必要的字符串转换和字符过滤,这会破坏二进制数据的完整性
- 使用
json.Marshal而不是json.NewEncoder来避免自动添加的换行符 - 根据实际需求选择合适的返回类型(String或Bytes)
- 保持二进制数据的原始格式,避免UTF-8转换


