go服务端导出.xlsx格式的Excel文件流给前端浏览器
上传文件(以".xlsx"类型的Excel为例)并读取数据请参考https://blog.youkuaiyun.com/fbbqt/article/details/106016895
import (
"github.com/tealeg/xlsx"
bm "github.com/bilibili/kratos/pkg/net/http/blademaster"
)
//生成操作日志Excel文件并返回给前端,具体逻辑实现
func GetOperationLogExcel(ctx *bm.Context, data []database.OperationLog) error {
//设置导出的字体类型和大小
xlsx.SetDefaultFont(18,"楷体")
style := &xlsx.Style{}
style.Fill = *xlsx.NewFill("solid", "EFEFDE", "EFEFDE")
style.Border = xlsx.Border{RightColor: "FF"}
file := xlsx.NewFile()
sheet, err := file.AddSheet("Sheet1")
if err != nil {
return err
}
//设置表格头
row := sheet.AddRow()
//row.SetHeightCM(1) //设置每行的高度
for _, title := range Title { //looping from 0 to the length of the array
cell := row.AddCell()
cell.Value = title
//设置字体为红色
cell.GetStyle().Font.Color = "00FF0000"
}
//写入数据
for _, content := range data {
row = sheet.AddRow()
//row.SetHeightCM(1) //设置每行的高度
cell := row.AddCell() //cell 1
cell.Value = content.Username //账户名称
cell = row.AddCell() //cell 2
cell.Value = content.Realname //姓名
cell = row.AddCell() //cell 3
cell.Value = content.Role //账户角色
cell = row.AddCell() //cell 4
cell.Value = content.OperationModule //操作模块
cell = row.AddCell() //cell 5
cell.Value = content.OperationDetail //详细操作
cell = row.AddCell() //cell 6
//content.OperationTime前面加',是因为在linux系统本地生成时默认带。
//但传递给windows前端生成excel的时候,会去掉',会导致格式变化,会变成跟Windows系统上的时间格式一样。
cell.Value = "'" + content.OperationTime //操作时间
cell = row.AddCell() //cell 7
cell.Value = content.OperationResult //操作结果
}
// 设置头信息:Content-Disposition ,消息头指示回复的内容该以何种形式展示,
// 是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地
// Content-Disposition: inline
// Content-Disposition: attachment
// Content-Disposition: attachment; filename="filename.后缀"
// 第一个参数或者是inline(默认值,表示回复中的消息体会以页面的一部分或者
// 整个页面的形式展示),或者是attachment(意味着消息体应该被下载到本地;
// 大多数浏览器会呈现一个“保存为”的对话框,将filename的值预填为下载后的文件名,
// 假如它存在的话)
ctx.Writer.Header().Add("Content-Disposition", `attachment; filename="OperationLog.xlsx"`)
//xls,设置后缀为xls类型的表格
//w.Header().Add("Content-Type", "application/vnd.ms-excel")
//xlsx,设置后缀为xlsx类型的表格
ctx.Writer.Header().Add("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
//eTime := time.Now().Format("2006-01-02 15:04:05")
//path := fmt.Sprint("/home/bqpbqt/nginx-file/", "_", eTime, ".xlsx") //生成excel文件到该服务目录下
//err = file.Save(path)
//if err != nil {
// return err
//}
先把文件保存在服务器上,然后通过 http.ServeFile返回给浏览器
//http.ServeFile(ctx.Writer,ctx.Request,path)
//通过静态路由访问静态目录(./data)下的文件
//path := fmt.Sprint("./data/financial/", "_", eTime, ".xlsx") //生成excel文件到该服务目录下
//return fmt.Sprint("/financial/", "_", eTime, ".xlsx")
//http.ServeContent采取的是直接把数据写入http.responsewriter (即http包中的responsewriter)中进行返回
var buffer bytes.Buffer
if e := file.Write(&buffer); e != nil {
return e
}
r := bytes.NewReader(buffer.Bytes())
///
//这块是调用GetFileType()函数,就可以拿到文件流的文件后缀
fSrc, _ := ioutil.ReadAll(r)
log.Info("ggggggggggg",GetFileType(fSrc[:10]))
//
//最主要的一句,返回给浏览器
http.ServeContent(ctx.Writer, ctx.Request, "", time.Now(), r)
return nil
}
///
//TODO 以下是判断文件流的文件后缀是不是 .xlsx
// 获取前面结果字节的二进制
func bytesToHexString(src []byte) string {
res := bytes.Buffer{}
if src == nil || len(src) <= 0 {
return ""
}
temp := make([]byte, 0)
for _, v := range src {
sub := v & 0xFF //其本质原因就是想保持二进制补码的一致性
hv := hex.EncodeToString(append(temp, sub))
if len(hv) < 2 {
res.WriteString(strconv.FormatInt(int64(0), 10))
}
res.WriteString(hv)
}
return res.String()
}
// 用文件前面几个字节来判断
// fSrc: 文件字节流(就用前面几个字节)
func GetFileType(fSrc []byte) string {
var fileType string
fileCode := bytesToHexString(fSrc)
log.Info("....", fileCode)
if strings.HasSuffix(fileCode, ".xlsx") {
fileType = ".xlsx"
log.Info("nnnnnnnnnnnnnnnnnn", fileType)
}
log.Info("mmmmmmmmmmmmmmmmmm", fileType)
return fileType
}
前端接收文件流
- 不管用什么方法,一定要记得设置responseType!!!,否则导出的文件报错,打不开
//xlsx类型的Excel文件要设置成arraybuffer类型,如下所示:
axios.post(url_post,params_post,{responseType: 'arraybuffer'}).then((res) => {}
- 若后端在往前端返回的时候,没有忽略失败时的错误,则需要考虑以下的操作:
设置返回的responseType类型是arraybuffer后,如果导出失败,本应返回的json格式数据也会变成arraybuffer,这种情况下就需要对这个流进行处理,判断它是导出失败的json,还是导出成功的流文件,所以在上面的导出代码之前添加转json并进行判断:
const tempBlob = new Blob( [res.data], {type: ‘application/json} )
// 通过 FileReader 读取这个 blob
const reader = new FileReader()
reader.onload = e => {
const res = e.target.result
// 此处对fileReader读出的结果进行JSON解析
// 可能会出现错误,需要进行捕获
try {
const json = JSON.parse(res)
if (json) {
// 解析成功说明后端导出出错,进行导出失败的操作,并直接返回
return
}
} catch (err) {
// 该异常为无法将字符串转为json
// 说明返回的数据是一个流文件
// 不需要处理该异常,只需要捕获即刻
}
// 如果代码能够执行到这里,说明后端给的是一个流文件,再执行上面导出的代码
// do export code
}
// 将blob对象以文本的方式读出,读出完成后将会执行 onload 方法
reader.readAsText(tempBlob)