文件
本地文件的读写
文件处理,一般指服务端对于文件的保存和读取
读写
go对文件的读写能力上,二进制>json>xml>文本txt>自定义文件
这里注意,如果是保存time.time,速度会下降,如果没有时间处理,速度接近二进制读写。
而文件的大小上,文件压缩后,不管是哪种格式,大小都是接近的。
json文件处理
http://c.biancheng.net/view/4545.html
写文件
package main
import (
"encoding/json"
"fmt"
"os"
)
type Website struct {
Name string `xml:"name,attr"`
Url string
Course []string
}
func main() {
info := []Website{{"Golang", "http://c.biancheng.net/golang/", []string{"http://c.biancheng.net/cplus/", "http://c.biancheng.net/linux_tutorial/"}}, {"Java", "http://c.biancheng.net/java/", []string{"http://c.biancheng.net/socket/", "http://c.biancheng.net/python/"}}}
// 创建文件
filePtr, err := os.Create("info.json")
if err != nil {
fmt.Println("文件创建失败", err.Error())
return
}
defer filePtr.Close()
// 创建Json编码器
encoder := json.NewEncoder(filePtr)
err = encoder.Encode(info)
if err != nil {
fmt.Println("编码错误", err.Error())
} else {
fmt.Println("编码成功")
}
}
读文件
package main
import (
"encoding/json"
"fmt"
"os"
)
type Website struct {
Name string `xml:"name,attr"`
Url string
Course []string
}
func main() {
filePtr, err := os.Open("./info.json")
if err != nil {
fmt.Println("文件打开失败 [Err:%s]", err.Error())
return
}
defer filePtr.Close()
var info []Website
// 创建json解码器
decoder := json.NewDecoder(filePtr)
err = decoder.Decode(&info)
if err != nil {
fmt.Println("解码失败", err.Error())
} else {
fmt.Println("解码成功")
fmt.Println(info)
}
}
服务端文件的上传和下载
文件上传
单个文件上传
采用的是gin框架,从gin.Context.FormFile(uploadFileKey)获取file,然后就是流的操作,这里采用了io工具类
下面示例中没有对file进行关闭
如果是采用ioutil可以将文件的内容读取
fileContent, err := ioutil.ReadAll(uploadFile)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"msg": "读取文件内容失败!" + err.Error(),
})
}
func FileUpload(ctx *gin.Context) {
header, err := ctx.FormFile("file")
if err != nil {
logger.Error("FileUpload:文件名参数格式不正确:", err)
util.Response(ctx, 500, "文件名参数格式不正确", err.Error())
return
}
fileName := header.Filename
fmt.Println("fileName:",fileName)
if err := SaveUploadedFile(header); err != nil {
logger.Error("FileUpload:保存失败:", err)
util.Response(ctx, 500, "保存失败", err.Error())
return
}
util.Response(ctx, 200, "ok", nil)
return
}
func SaveUploadedFile(fileHeader *multipart.FileHeader) error {
fileName := fileHeader.Filename
//得到file
inFle, err := fileHeader.Open()
if err != nil {
return err
}
defer inFle.Close()
//创建 dst 文件
out, err := os.Create("D:\\download\\"+fileName)
if err != nil {
return err
}
defer out.Close()
// 拷贝文件
_, err = io.Copy(out, inFle)
}
如果是表单填写,获取file的方式如下
type newForm struct {
UploadKey *multipart.FileHeader `form:"upload-key"`
Name string `form:"name"`
Age int `form:"age"`
}
var form newForm
if err := c.ShouldBind(&form); err != nil{
//ignore
}
重复操作file,会发现实现了文件的覆盖,所以需要移动读取的位移inFle.Seek(0, 0)
func SaveUploadedFile(fileHeader *multipart.FileHeader) error {
fileName := fileHeader.Filename
//得到file
inFle, err := fileHeader.Open()
if err != nil {
return err
}
defer inFle.Close()
//port, ip, err := util.GetRemoteServiceInfo("file-open-service")
//if err != nil {
// return err
//}
//url := "http://" + ip + ":" + port + "/attachment/upload/sfOss"
md5 := md5.New()
io.Copy(md5, inFle)
MD5Str := hex.EncodeToString(md5.Sum(nil))
inFle.Seek(0, 0)
fmt.Println("filepath.Base(fileName):", filepath.Base(fileName))
var p = make(map[string]string)
p["type"] = "3"
p["md5"] = MD5Str
var header = make(map[string]string)
header["x-user-id"] = "11"
header["x-account"] = "00"
fileHttp, err := FileHttp("http://localhost:8099/test/file/upload", inFle, "file", filepath.Base(fileName), p, header)
var result = make(map[string]interface{})
json.Unmarshal(fileHttp, &result)
return err
}
func FileHttp(url string, file multipart.File, fieldName, fileName string, formParams, header map[string]string) ([]byte, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(fieldName, fileName)
if err != nil {
return nil, err
}
_, err = io.Copy(part, file)
for k, v := range formParams {
if err := writer.WriteField(k, v); err != nil {
return nil, err
}
}
if err := writer.Close(); err != nil {
return nil, err
}
//post请求
req, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return nil, err
}
//本地启动一个带头
if header != nil && len(header) > 0 {
for k, v := range header {
req.Header.Set(k, v)
}
}
req.Header.Add("Content-Type", writer.FormDataContentType())
client := &http.Client{}
fmt.Println("执行请求")
resp, err := client.Do(req)
if err != nil {
fmt.Printf("error to request to the server:%s\n", err.Error())
return nil, err
}
fmt.Println("执行请求结束")
defer resp.Body.Close()
respBody, _ := ioutil.ReadAll(resp.Body)
fmt.Println("resp.Body:", string(respBody))
return respBody, err
}
大文件流上传
https://www.cnblogs.com/ahfuzhang/p/12629416.html
文件下载
关于gin框架文件的下载,网上有一个方案是返回文件的地址,https://www.jianshu.com/p/6950550b2937,
代码如下
//TODO Test资源文件下载
func DownloadFileService(c *gin.Context) {
fileDir := c.Query("fileDir")
fileName := c.Query("fileName")
//打开文件
_, errByOpenFile := os.Open(fileDir + "/" + fileName)
//非空处理
if common.IsEmpty(fileDir) || common.IsEmpty(fileName) || errByOpenFile != nil {
/*c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "失败",
"error": "资源不存在",
})*/
c.Redirect(http.StatusFound, "/404")
return
}
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename="+fileName)
c.Header("Content-Transfer-Encoding", "binary")
c.File(fileDir + "/" + fileName)
return
}
上面的方式有一个很大的弊端,就是一定要先对文件进行下载,而且客户端必须能否访问,但是有时候项目是没有文件系统的,所以参考https://www.jianshu.com/p/c9961b9d48df的http接口,下面代码从gin框架获取responseWriter 实现文件的下载。
responseWriter := context.Writer
fileName :="image.png"
file, err := os.Open("image.png")
if err==nil {
responseWriter.Header().Add("Content-type", "application/octet-stream")
responseWriter.Header().Add("content-disposition", "attachment; filename=\""+fileName+"\"")
_, err := io.Copy(responseWriter, file)
if err==nil {
responseWriter.Flush()
}
}
将数据压缩然后下载
func JsonFileDownload(ctx *gin.Context) {
responseWriter := ctx.Writer
zipBuf:= new(bytes.Buffer)
zipWriter := zip.NewWriter(zipBuf)
content := "{}"
fileName := "test.json"
fWriter, err := zipWriter.Create(fileName)
if err!=nil {
logger.Error("zipWriter创建压缩文件失败:",err)
ctx.Status(http.StatusInternalServerError)
return
}
_, err = fWriter.Write([]byte(content))
if err!=nil {
logger.Error("fWriter创建压缩文件失败:",err)
ctx.Status(http.StatusInternalServerError)
return
}
err = zipWriter.Close()
if err!=nil {
logger.Error("关闭zip失败:",err)
ctx.Status(http.StatusInternalServerError)
return
}
zipName :="test.zip"
responseWriter.Header().Add("Content-type", "application/octet-stream")
responseWriter.Header().Add("content-disposition", "attachment; filename=\""+zipName+"\"")
_, err = io.Copy(responseWriter, zipBuf)
if err == nil {
ctx.Status(http.StatusOK)
responseWriter.Flush()
} else {
logger.Error("转换流异常:", err)
ctx.Status(http.StatusInternalServerError)
responseWriter.Flush()
}
return
}
文件下载遇到的问题
文件名乱码问题
java是这样解决的
response.setHeader("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes("utf-8"), "ISO_8859_1"));
response.addHeader("Content-Length", "" + fileMetaDataVO.getFileSize());
response.setContentType("application/octet-stream");
IOUtils.copy(inputStream, outputStream);
func Utf8ToGbk(s []byte) (string, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder())
d, err := ioutil.ReadAll(reader)
if err != nil {
return "", err
}
return string(d), nil
}
实现zip文件导出和实现zip文件的导入,数据文件是json文件
/**
* 下载分组的json文件
*/
func JsonFileDownload(ctx *gin.Context) {
userId := ctx.Request.Header.Get(constant.X_USER_ID)
companyId := ctx.Request.Header.Get(constant.X_COMPANY_ID)
account := ctx.Request.Header.Get(constant.SESSION_ACCOUNT)
user := param.UserInfo{Account: account, UserId: userId, CompanyId: companyId}
//权限的判断
if !GetAuth("", account, userId, companyId) {
logger.Error(account, ":AddConfigByExcel:没有权限")
util.Response(ctx, 500, "没有权限", nil)
return
}
responseWriter := ctx.Writer
zipBuf := new(bytes.Buffer)
zipWriter := zip.NewWriter(zipBuf)
content := "{}"
fileName := "test.json"
fWriter, err := zipWriter.Create(fileName)
if err != nil {
logger.Error(user.Account, ":zipWriter创建压缩文件失败:", err)
ctx.Status(http.StatusInternalServerError)
return
}
_, err = fWriter.Write([]byte(content))
if err != nil {
logger.Error(user.Account, ":fWriter创建压缩文件失败:", err)
ctx.Status(http.StatusInternalServerError)
return
}
err = zipWriter.Close()
if err != nil {
logger.Error(user.Account, ":关闭zip失败:", err)
ctx.Status(http.StatusInternalServerError)
return
}
zipName := "test.zip"
responseWriter.Header().Add("Content-type", "application/octet-stream")
responseWriter.Header().Add("content-disposition", "attachment; filename=\""+zipName+"\"")
_, err = io.Copy(responseWriter, zipBuf)
if err == nil {
ctx.Status(http.StatusOK)
responseWriter.Flush()
} else {
logger.Error(user.Account, ":转换流异常:", err)
ctx.Status(http.StatusInternalServerError)
responseWriter.Flush()
}
return
}
/**
* 上传分组的json文件
*/
func JsonFileUpload(ctx *gin.Context) {
userId := ctx.Request.Header.Get(constant.X_USER_ID)
companyId := ctx.Request.Header.Get(constant.X_COMPANY_ID)
account := ctx.Request.Header.Get(constant.SESSION_ACCOUNT)
user := param.UserInfo{Account: account, UserId: userId, CompanyId: companyId}
//权限的判断
if !GetAuth("", account, userId, companyId) {
logger.Error(user.Account, ":AddConfigByExcel:没有权限")
util.Response(ctx, 500, "没有权限", nil)
return
}
//读取第一fileName的文件
fileHeader, err := ctx.FormFile("file")
if err != nil {
logger.Error(account, ":文件格式错误"+err.Error())
util.Response(ctx, 500, "文件格式错误"+err.Error(), nil)
return
}
file, err := fileHeader.Open()
if err != nil {
logger.Error(account, ":文件格式错误"+err.Error())
util.Response(ctx, 500, "文件格式错误"+err.Error(), nil)
return
}
defer file.Close()
// 打开一个zip格式文件
//r, err := zip.OpenReader("file.zip")
zipReader, err := zip.NewReader(file, fileHeader.Size)
if err != nil {
logger.Error(account, ":压缩包读取错误"+err.Error())
util.Response(ctx, 500, "压缩包读取错误"+err.Error(), nil)
return
}
//defer zipReader.Close()
if len(zipReader.File) == 0 {
logger.Error(account, ":没有分组文件"+err.Error())
util.Response(ctx, 500, "没有分组文件:"+err.Error(), nil)
return
}
for _, fileInfo := range zipReader.File {
logger.Info("文件名:", fileInfo.Name)
fileReader, err := fileInfo.Open()
if err != nil {
logger.Error("fileInfo err:", err)
util.Response(ctx, 500, "fileInfo err"+err.Error(), nil)
return
}
decoder := json.NewDecoder(fileReader)
var result = make(map[string]interface{})
err = decoder.Decode(&result)
if err != nil {
logger.Error("fileInfo 结构体解析错误:", err)
util.Response(ctx, 500, "fileInfo 结构体解析错误:"+err.Error(), nil)
return
}
fileReader.Close()
logger.Info("文件内容:", util.GetJson(result))
}
return
}
Multipart client实现
文件流的请求
func FileHttp(url string, file multipart.File, fieldName, fileName string, formParams, header map[string]string) ([]byte, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(fieldName, fileName)
if err != nil {
return nil, err
}
_, err = io.Copy(part, file)
for k, v := range formParams {
if err := writer.WriteField(k, v); err != nil {
return nil, err
}
}
if err := writer.Close(); err != nil {
return nil, err
}
//post请求
req, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return nil, err
}
//本地启动一个带头
if header != nil && len(header) > 0 {
for k, v := range header {
req.Header.Set(k, v)
}
}
req.Header.Add("Content-Type", writer.FormDataContentType())
client := &http.Client{}
fmt.Println("执行请求")
resp, err := client.Do(req)
if err != nil {
fmt.Printf("error to request to the server:%s\n", err.Error())
return nil, err
}
fmt.Println("执行请求结束")
defer resp.Body.Close()
respBody, _ := ioutil.ReadAll(resp.Body)
fmt.Println("resp.Body:", string(respBody))
return respBody, err
}