Vue + ElementUI +Go-admin 上传、下载、预览文件
前端
上传-不使用el-upload组件
upload(row) {
// 1、未审核时,上传也出现两个row的情况
// 2、在驳回=>撤销驳回=>再上传的时候,会出现row有两个对象的情况
// row只有上传的时候不正常,其他驳回、撤销都正常
// 出问题时直接打印row,发现传过来的也是两个对象。
if (row.rejectTag === '已驳回') {
alert('该报告已被驳回,请先撤销驳回')
return
}
console.log('文件内容', row.files, '状态标签', row.rejectTag, '报告id', row.reportId)
if (row.files !== 'rejected clean' && row.files !== 'null' && row.files !== '') {
alert('您已上传侵权报告')
return
}
this.$refs.uploadInput.click()
console.log(this.$refs)
this.$refs.uploadInput.onchange = e => {
const formData = new FormData()
formData.append('file', e.target.files[0])
Upload(formData).then(res => {
const path = res.data.data.path
const id = row.reportId
updateReport({
reportId: id,
filesOpt: 'add',
files: [path]
}).then(res => {
console.log(res.data)
this.getList()
setTimeout(() => {
// 这样才能正确关闭
this.msg = this.$message({
duration: 1000,
type: 'success',
message: '提交成功'
})
}, 0)
})
})
}
}
this.$refs
ref 写在标签上时:this.$refs.xxx
获取的是添加了ref="xxx"
标签对应的dom元素
<el-button icon="el-icon-upload" size="small" type="primary" @click="handleUploadFile">
上传文件
<input v-show="false" ref="uploadInput" type="file">
</el-button>
如果这样写,会出现错误,不报错,但是结果会永远显示row=3的时候的数据,这是为什么呢?
同时如果通过搜索得到一行数据,我们也不能在搜索的结果上传,这是为什么呢?
因为我们在一整个el-table中只需要一个上传键,一次一个用户只能点一个按钮,一个按钮对应一个文件上传,所以整个文件不需要在遍历罗列上传按钮的时候把this.refs也遍历罗列,只需要一个就可以了。所以把<input v-show="false" ref="uploadInput" type="file">
放在所有el-table的前面,事先声明且跟el-table是平级的。
this.refs如果写在上传的el-button里面,则打印this.refs的时候也会出现两次ref的名字。也就是调用了两次this.ref指向的dom元素(input),所以我们对于this.refs进行操作的时候,也操作了两次(click、onchange等函数里写到的操作),所以返回来的也是两个对象。而最后改动的数据是第二个对象(错误带回的对象,并不是原来需要操作的对象),导致即录入不了数据库,也无法上传成功。
上传-使用el-upload 组件
预览-仅支持图片预览
其他的预览需要office或者wps的一些插件支持,暂时没有研究
下载-axios
暂时没有研究
后端
1 文件上传
type FileResponse struct {
Size int64 `json:"size"`
Path string `json:"path"`
FullPath string `json:"full_path"`
Name string `json:"name"`
Type string `json:"type"`
}
const path = "你的文件地址"
type File struct {
api.Api
}
// UploadFile 上传图片
// @Summary 上传图片
// @Description 获取form-data
// @Tags 公共接口
// @Accept multipart/form-data
// @Param type query string true "type" (1:单图,2:多图, 3:base64图片)
// @Param file formData file true "file"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router 你的路径
// @Security Bearer
func (e File) UploadFile(c *gin.Context) {
e.MakeContext(c)
tag, _ := c.GetPostForm("type")
urlPrefix := fmt.Sprintf("http://%s/", c.Request.Host)
var fileResponse FileResponse
switch tag {
case "1": // 单图
var done bool
fileResponse, done = e.singleFile(c, fileResponse, urlPrefix)
if done {
return
}
e.OK(fileResponse, "上传成功")
return
case "2": // 多图
multipartFile := e.multipleFile(c, urlPrefix)
e.OK(multipartFile, "上传成功")
return
case "3": // base64
fileResponse = e.baseImg(c, fileResponse, urlPrefix)
e.OK(fileResponse, "上传成功")
default:
var done bool
fileResponse, done = e.singleFile(c, fileResponse, urlPrefix)
if done {
return
}
e.OK(fileResponse, "上传成功")
return
}
}
func (e File) baseImg(c *gin.Context, fileResponse FileResponse, urlPerfix string) FileResponse {
files, _ := c.GetPostForm("file")
file2list := strings.Split(files, ",")
ddd, _ := base64.StdEncoding.DecodeString(file2list[1])
guid := uuid.New().String()
fileName := guid + ".jpg"
err := utils.IsNotExistMkDir(path)
if err != nil {
e.Error(500, errors.New(""), "初始化文件路径失败")
}
base64File := path + fileName
_ = ioutil.WriteFile(base64File, ddd, 0666)
typeStr := strings.Replace(strings.Replace(file2list[0], "data:", "", -1), ";base64", "", -1)
fileResponse = FileResponse{
Size: pkg.GetFileSize(base64File),
Path: base64File,
FullPath: urlPerfix + base64File,
Name: "",
Type: typeStr,
}
source, _ := c.GetPostForm("source")
err = thirdUpload(source, fileName, base64File)
if err != nil {
e.Error(200, errors.New(""), "上传第三方失败")
return fileResponse
}
if source != "1" {
fileResponse.Path = "/static/uploadfile/" + fileName
fileResponse.FullPath = "/static/uploadfile/" + fileName
}
return fileResponse
}
func (e File) multipleFile(c *gin.Context, urlPerfix string) []FileResponse {
files := c.Request.MultipartForm.File["file"]
source, _ := c.GetPostForm("source")
var multipartFile []FileResponse
for _, f := range files {
guid := uuid.New().String()
fileName := guid + "." + f.Filename
err := utils.IsNotExistMkDir(path)
if err != nil {
e.Error(500, errors.New(""), "初始化文件路径失败")
}
multipartFileName := path + fileName
err1 := c.SaveUploadedFile(f, multipartFileName)
fileType, _ := utils.GetType(multipartFileName)
if err1 == nil {
err := thirdUpload(source, fileName, multipartFileName)
if err != nil {
e.Error(500, errors.New(""), "上传第三方失败")
} else {
fileResponse := FileResponse{
Size: pkg.GetFileSize(multipartFileName),
Path: multipartFileName,
FullPath: urlPerfix + multipartFileName,
Name: f.Filename,
Type: fileType,
}
if source != "1" {
fileResponse.Path = "/static/uploadfile/" + fileName
fileResponse.FullPath = "/static/uploadfile/" + fileName
}
multipartFile = append(multipartFile, fileResponse)
}
}
}
return multipartFile
}
func (e File) singleFile(c *gin.Context, fileResponse FileResponse, urlPerfix string) (FileResponse, bool) {
files, err := c.FormFile("file")
if err != nil {
e.Error(200, errors.New(""), "图片不能为空")
return FileResponse{}, true
}
// 上传文件至指定目录
guid := uuid.New().String()
fileName := guid + "." + files.Filename
err = utils.IsNotExistMkDir(path)
if err != nil {
e.Error(500, errors.New(""), "初始化文件路径失败")
}
singleFile := path + fileName
_ = c.SaveUploadedFile(files, singleFile)
fileType, _ := utils.GetType(singleFile)
fileResponse = FileResponse{
Size: pkg.GetFileSize(singleFile),
Path: singleFile,
FullPath: urlPerfix + singleFile,
Name: files.Filename,
Type: fileType,
}
//source, _ := c.GetPostForm("source")
//err = thirdUpload(source, fileName, singleFile)
//if err != nil {
// e.Error(200, errors.New(""), "上传第三方失败")
// return FileResponse{}, true
//}
fileResponse.Path = "/static/uploadfile/" + fileName
fileResponse.FullPath = "/static/uploadfile/" + fileName
return fileResponse, false
}
func thirdUpload(source string, name string, path string) error {
switch source {
case "2":
return ossUpload("img/"+name, path)
case "3":
return qiniuUpload("img/"+name, path)
}
return nil
}
func ossUpload(name string, path string) error {
oss := file_store.ALiYunOSS{}
return oss.UpLoad(name, path)
}
func qiniuUpload(name string, path string) error {
oss := file_store.ALiYunOSS{}
return oss.UpLoad(name, path)
}
2 将文件地址名称存入对应数据库库,修改字段
- dto中
需要对收到的结构体修改,留出文件和文件操作的字段
type ReportGetPageReq struct {
//省略不必要字段....
FilesOpt string `json:"filesOpt" comment:"文件操作"`
Files []string `json:"files" comment:"报告文件"`
}
func (s *ReportGetPageReq) GenerateNoneFile(model *model.Report) {
// 不要写 model.files = s.files
// 因为s的files是字符串数组,而model的files是字符串,无法相等
}
type InnerFile struct {
FileName string `json:"FileName"`
FilePath string `json:"FilePath"`
}
//newInnerFiles这个函数生成了文件这个结构体?(文件+路径)
func newInnerFiles(files ...string) []*InnerFile {
res := make([]*InnerFile, 0, len(files))
for _, f := range files {
tmp := strings.Split(f, "/")
fmt.Println(tmp)
fn := strings.Join(strings.Split(tmp[len(tmp)-1], ".")[1:], ".")
fmt.Println(fn)
res = append(res, &InnerFile{
FileName: fn,
FilePath: path.Join(my_config.CurrentPatentConfig.FileUrl, f),
})
}
return res
}
//生成结构体并添加文件or在原有文件切片后添加文件
func (s *ReportGetPageReq) GenerateAndAddFiles(model *model.Report) {
s.Generate(model)
if len(model.Files) == 0 {
innerFiles := newInnerFiles(s.Files...)
fbs, _ := json.Marshal(innerFiles)
// returns the JSON encoding of v 输入v,遍历v,返回byte[]
model.Files = string(fbs)
} else { // 长度大于0,append,不是大于1
files := make([]*InnerFile, 0)
_ = json.Unmarshal([]byte(model.Files), &files)
// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
innerFiles := newInnerFiles(s.Files...)
// 以下步骤相同
innerFiles = append(innerFiles, files...)
fbs, _ := json.Marshal(innerFiles)
model.Files = string(fbs)
}
}
```go
//生成结构体,接住删除 *部分文件* 后的files
func (s *ReportGetPageReq) GenerateAndDeleteFiles(model *model.Report) {
//我们需要根据s.Files去删除数据库中(model.Files)里的文件,所以如果s.Files空,就不会删除文件!
//此处是全删,就是把model.Files整个放到s.Files里,也可以选择部分删除
//需要注意s.Files是string的切片,model.Files只是string,需要Unmarshal格式化为结构体struct,而且元素为FilePath
s.Generate(model)
if len(model.Files) != 0 {
files := make([]*InnerFile, 0)
_ = json.Unmarshal([]byte(model.Files), &files)
// 把原始文件值存在&files里(Unmarshal函数做的数据结构很好看)
needToDel := make(map[string]struct{})
//files[i]的FilePath就是s.Files的元素,手动格式化s.Files
s.Files = []string{}
for i, _ := range files {
s.Files = append(s.Files, files[i].FilePath)
//学习此处切片append的go的用法
}
for _, df := range s.Files {
// 遍历s.Files,写入needToDel
needToDel[df] = struct{}{}
fmt.Println("needToDel", needToDel) //如果这里不打印,没有进来,就因为s.Files是空的 √
}
slow := 0
for _, f := range files {
// 此处files是unmarshal来的原始切片,判断 key 是否在 map 里 if _, ok := map[key];
// ok 是 true 则 正是needToDel的元素;
// ok 是 false 则 slow++,此时该元素f要保留,所以需要在files里面写入f
if _, ok := needToDel[f.FilePath]; !ok {
fmt.Println(needToDel[f.FilePath])
files[slow] = f
slow++
}
}
files = files[:slow] //截取从头到slow的切片,不包括下标slow
fmt.Println(&files)
fbs, _ := json.Marshal(files)
model.Files = string(fbs)
}
}
- 在删除的过程中出现数据库未更新的情况,debug后发现是数据结构不匹配
- 参考数据结构如下
说过经过json.Unmarshal的model.Files数据结构 files 非常好看,如下
- files作为指针切片,可以用
files[i].FilePath
访问
报错
- Proxy error: Could not proxy request /login from
- vue.config.js proxy地址检查,即使写了部署的服务器的地址,也要ping的通才能打开
- runtime error: index out of range [0] with length 0
- 大部分情况就是没有初始化数组就直接用了,比如list[0]但是没写make语句,举例
list := make([]model.Report, 0)
或者
req1.ReportIds = make([]int, len(list))
- request body not present anymore
- 一个api进行两次查询使用不同的req结构体时,需要重新绑定req并且排查err