Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
490bb901
authored
Feb 04, 2024
by
owen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
基础设施:增加前端直连上传文件到S3服务的功能
parent
64cfcbf6
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
152 additions
and
41 deletions
+152
-41
.env.dev
+2
-0
.env.local
+2
-0
.env.prod
+2
-0
.env.stage
+2
-0
.env.test
+2
-0
src/api/infra/file/index.ts
+21
-0
src/components/UploadFile/src/UploadFile.vue
+10
-12
src/components/UploadFile/src/UploadImg.vue
+4
-8
src/components/UploadFile/src/UploadImgs.vue
+4
-9
src/components/UploadFile/src/useUpload.ts
+87
-0
src/views/infra/file/FileForm.vue
+5
-10
src/views/infra/file/index.vue
+11
-2
No files found.
.env.dev
View file @
490bb901
...
...
@@ -7,6 +7,8 @@ VITE_DEV=true
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# VITE_BASE_URL='http://dofast.demo.huizhizao.vip:20001'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
...
...
.env.local
View file @
490bb901
...
...
@@ -6,6 +6,8 @@ VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://localhost:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
...
...
.env.prod
View file @
490bb901
...
...
@@ -6,6 +6,8 @@ VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://localhost:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
...
...
.env.stage
View file @
490bb901
...
...
@@ -6,6 +6,8 @@ VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
...
...
.env.test
View file @
490bb901
...
...
@@ -6,6 +6,8 @@ VITE_DEV=false
# 请求路径
VITE_BASE_URL
=
'http://localhost:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE
=
server
# 上传路径
VITE_UPLOAD_URL
=
'http://localhost:48080/admin-api/infra/file/upload'
...
...
src/api/infra/file/index.ts
View file @
490bb901
...
...
@@ -6,6 +6,14 @@ export interface FilePageReqVO extends PageParam {
createTime
?:
Date
[]
}
// 文件预签名地址 Response VO
export
interface
FilePresignedUrlRespVO
{
// 文件配置编号
configId
:
number
// 文件预签名地址
url
:
string
}
// 查询文件列表
export
const
getFilePage
=
(
params
:
FilePageReqVO
)
=>
{
return
request
.
get
({
url
:
'/infra/file/page'
,
params
})
...
...
@@ -15,3 +23,16 @@ export const getFilePage = (params: FilePageReqVO) => {
export
const
deleteFile
=
(
id
:
number
)
=>
{
return
request
.
delete
({
url
:
'/infra/file/delete?id='
+
id
})
}
// 获取文件预签名地址
export
const
getFilePresignedUrl
=
(
fileName
:
string
)
=>
{
return
request
.
get
<
FilePresignedUrlRespVO
>
({
url
:
'/infra/file/presigned-url'
,
params
:
{
fileName
}
})
}
// 创建文件
export
const
createFile
=
(
data
:
any
)
=>
{
return
request
.
post
({
url
:
'/infra/file/create'
,
data
})
}
src/components/UploadFile/src/UploadFile.vue
View file @
490bb901
...
...
@@ -3,11 +3,10 @@
<el-upload
ref=
"uploadRef"
v-model:file-list=
"fileList"
:action=
"up
date
Url"
:action=
"up
load
Url"
:auto-upload=
"autoUpload"
:before-upload=
"beforeUpload"
:drag=
"drag"
:headers=
"uploadHeaders"
:limit=
"props.limit"
:multiple=
"props.limit > 1"
:on-error=
"excelUploadError"
...
...
@@ -16,6 +15,7 @@
:on-remove=
"handleRemove"
:on-success=
"handleFileSuccess"
:show-file-list=
"true"
:http-request=
"httpRequest"
class=
"upload-file-uploader"
name=
"file"
>
...
...
@@ -36,9 +36,10 @@
</template>
<
script
lang=
"ts"
setup
>
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
getAccessToken
,
getTenantId
}
from
'@/utils/auth'
import
type
{
UploadInstance
,
UploadProps
,
UploadRawFile
,
UploadUserFile
}
from
'element-plus'
import
{
isString
}
from
'@/utils/is'
import
{
useUpload
}
from
'@/components/UploadFile/src/useUpload'
import
{
UploadFile
}
from
'element-plus/es/components/upload/src/upload'
defineOptions
({
name
:
'UploadFile'
})
...
...
@@ -48,7 +49,6 @@ const emit = defineEmits(['update:modelValue'])
const
props
=
defineProps
({
modelValue
:
propTypes
.
oneOfType
<
string
|
string
[]
>
([
String
,
Array
<
String
>
]).
isRequired
,
title
:
propTypes
.
string
.
def
(
'文件上传'
),
updateUrl
:
propTypes
.
string
.
def
(
import
.
meta
.
env
.
VITE_UPLOAD_URL
),
fileType
:
propTypes
.
array
.
def
([
'doc'
,
'xls'
,
'ppt'
,
'txt'
,
'pdf'
]),
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileSize
:
propTypes
.
number
.
def
(
5
),
// 大小限制(MB)
limit
:
propTypes
.
number
.
def
(
5
),
// 数量限制
...
...
@@ -62,10 +62,8 @@ const uploadRef = ref<UploadInstance>()
const
uploadList
=
ref
<
UploadUserFile
[]
>
([])
const
fileList
=
ref
<
UploadUserFile
[]
>
([])
const
uploadNumber
=
ref
<
number
>
(
0
)
const
uploadHeaders
=
ref
({
Authorization
:
'Bearer '
+
getAccessToken
(),
'tenant-id'
:
getTenantId
()
})
const
{
uploadUrl
,
httpRequest
}
=
useUpload
()
// 文件上传之前判断
const
beforeUpload
:
UploadProps
[
'beforeUpload'
]
=
(
file
:
UploadRawFile
)
=>
{
...
...
@@ -120,10 +118,10 @@ const excelUploadError: UploadProps['onError'] = (): void => {
message
.
error
(
'导入数据失败,请您重新上传!'
)
}
// 删除上传文件
const
handleRemove
=
(
file
)
=>
{
const
f
index
=
fileList
.
value
.
map
((
f
)
=>
f
.
name
).
indexOf
(
file
.
name
)
if
(
f
index
>
-
1
)
{
fileList
.
value
.
splice
(
f
index
,
1
)
const
handleRemove
=
(
file
:
UploadFile
)
=>
{
const
index
=
fileList
.
value
.
map
((
f
)
=>
f
.
name
).
indexOf
(
file
.
name
)
if
(
index
>
-
1
)
{
fileList
.
value
.
splice
(
index
,
1
)
emitUpdateModelValue
()
}
}
...
...
src/components/UploadFile/src/UploadImg.vue
View file @
490bb901
...
...
@@ -3,15 +3,15 @@
<el-upload
:id=
"uuid"
:accept=
"fileType.join(',')"
:action=
"up
date
Url"
:action=
"up
load
Url"
:before-upload=
"beforeUpload"
:class=
"['upload', drag ? 'no-border' : '']"
:drag=
"drag"
:headers=
"uploadHeaders"
:multiple=
"false"
:on-error=
"uploadError"
:on-success=
"uploadSuccess"
:show-file-list=
"false"
:http-request=
"httpRequest"
>
<template
v-if=
"modelValue"
>
<img
:src=
"modelValue"
class=
"upload-image"
/>
...
...
@@ -50,8 +50,8 @@ import type { UploadProps } from 'element-plus'
import
{
generateUUID
}
from
'@/utils'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
getAccessToken
,
getTenantId
}
from
'@/utils/auth'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
useUpload
}
from
'@/components/UploadFile/src/useUpload'
defineOptions
({
name
:
'UploadImg'
})
...
...
@@ -70,7 +70,6 @@ type FileTypes =
// 接受父组件参数
const
props
=
defineProps
({
modelValue
:
propTypes
.
string
.
def
(
''
),
updateUrl
:
propTypes
.
string
.
def
(
import
.
meta
.
env
.
VITE_UPLOAD_URL
),
drag
:
propTypes
.
bool
.
def
(
true
),
// 是否支持拖拽上传 ==> 非必传(默认为 true)
disabled
:
propTypes
.
bool
.
def
(
false
),
// 是否禁用上传组件 ==> 非必传(默认为 false)
fileSize
:
propTypes
.
number
.
def
(
5
),
// 图片大小限制 ==> 非必传(默认为 5M)
...
...
@@ -101,10 +100,7 @@ const deleteImg = () => {
emit
(
'update:modelValue'
,
''
)
}
const
uploadHeaders
=
ref
({
Authorization
:
'Bearer '
+
getAccessToken
(),
'tenant-id'
:
getTenantId
()
})
const
{
uploadUrl
,
httpRequest
}
=
useUpload
()
const
editImg
=
()
=>
{
const
dom
=
document
.
querySelector
(
`#
${
uuid
.
value
}
.el-upload__input`
)
...
...
src/components/UploadFile/src/UploadImgs.vue
View file @
490bb901
...
...
@@ -3,16 +3,16 @@
<el-upload
v-model:file-list=
"fileList"
:accept=
"fileType.join(',')"
:action=
"up
date
Url"
:action=
"up
load
Url"
:before-upload=
"beforeUpload"
:class=
"['upload', drag ? 'no-border' : '']"
:drag=
"drag"
:headers=
"uploadHeaders"
:limit=
"limit"
:multiple=
"true"
:on-error=
"uploadError"
:on-exceed=
"handleExceed"
:on-success=
"uploadSuccess"
:http-request=
"httpRequest"
list-type=
"picture-card"
>
<div
class=
"upload-empty"
>
...
...
@@ -50,7 +50,7 @@ import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
import
{
ElNotification
}
from
'element-plus'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
getAccessToken
,
getTenantId
}
from
'@/utils/auth
'
import
{
useUpload
}
from
'@/components/UploadFile/src/useUpload
'
defineOptions
({
name
:
'UploadImgs'
})
...
...
@@ -70,7 +70,6 @@ type FileTypes =
const
props
=
defineProps
({
modelValue
:
propTypes
.
oneOfType
<
string
|
string
[]
>
([
String
,
Array
<
String
>
]).
isRequired
,
updateUrl
:
propTypes
.
string
.
def
(
import
.
meta
.
env
.
VITE_UPLOAD_URL
),
drag
:
propTypes
.
bool
.
def
(
true
),
// 是否支持拖拽上传 ==> 非必传(默认为 true)
disabled
:
propTypes
.
bool
.
def
(
false
),
// 是否禁用上传组件 ==> 非必传(默认为 false)
limit
:
propTypes
.
number
.
def
(
5
),
// 最大图片上传数 ==> 非必传(默认为 5张)
...
...
@@ -81,10 +80,7 @@ const props = defineProps({
borderradius
:
propTypes
.
string
.
def
(
'8px'
)
// 组件边框圆角 ==> 非必传(默认为 8px)
})
const
uploadHeaders
=
ref
({
Authorization
:
'Bearer '
+
getAccessToken
(),
'tenant-id'
:
getTenantId
()
})
const
{
uploadUrl
,
httpRequest
}
=
useUpload
()
const
fileList
=
ref
<
UploadUserFile
[]
>
([])
const
uploadNumber
=
ref
<
number
>
(
0
)
...
...
@@ -121,7 +117,6 @@ const emit = defineEmits<UploadEmits>()
const
uploadSuccess
:
UploadProps
[
'onSuccess'
]
=
(
res
:
any
):
void
=>
{
message
.
success
(
'上传成功'
)
// 删除自身
debugger
const
index
=
fileList
.
value
.
findIndex
((
item
)
=>
item
.
response
?.
data
===
res
.
data
)
fileList
.
value
.
splice
(
index
,
1
)
uploadList
.
value
.
push
({
name
:
res
.
data
,
url
:
res
.
data
})
...
...
src/components/UploadFile/src/useUpload.ts
0 → 100644
View file @
490bb901
import
{
getAccessToken
,
getTenantId
}
from
'@/utils/auth'
import
*
as
FileApi
from
'@/api/infra/file'
import
CryptoJS
from
'crypto-js'
import
{
UploadRawFile
,
UploadRequestOptions
}
from
'element-plus/es/components/upload/src/upload'
import
{
ajaxUpload
}
from
'element-plus/es/components/upload/src/ajax'
import
axios
from
'axios'
export
const
useUpload
=
()
=>
{
// 后端上传地址
const
uploadUrl
=
import
.
meta
.
env
.
VITE_UPLOAD_URL
// 是否使用前端直连上传
const
isClientUpload
=
UPLOAD_TYPE
.
CLIENT
===
import
.
meta
.
env
.
VITE_UPLOAD_TYPE
// 重写ElUpload上传方法
const
httpRequest
=
async
(
options
:
UploadRequestOptions
)
=>
{
// 模式一:前端上传
if
(
isClientUpload
)
{
// 1.1 生成文件名称
const
fileName
=
await
generateFileName
(
options
.
file
)
// 1.2 获取文件预签名地址
const
presignedInfo
=
await
FileApi
.
getFilePresignedUrl
(
fileName
)
// 1.3 上传文件(不能使用ElUpload的ajaxUpload方法的原因:其使用的是FormData上传,Minio不支持)
return
axios
.
put
(
presignedInfo
.
url
,
options
.
file
).
then
(()
=>
{
// 1.4. 记录文件信息到后端
const
fileVo
=
createFile
(
presignedInfo
.
configId
,
fileName
,
presignedInfo
.
url
,
options
.
file
)
// 通知成功,数据格式保持与后端上传的返回结果一致
return
{
data
:
fileVo
.
url
}
})
}
else
{
// 模式二:后端上传(需要增加后端身份认证请求头)
options
.
headers
[
'Authorization'
]
=
'Bearer '
+
getAccessToken
()
options
.
headers
[
'tenant-id'
]
=
getTenantId
()
// 使用ElUpload的上传方法
return
ajaxUpload
(
options
)
}
}
return
{
uploadUrl
,
httpRequest
}
}
/**
* 创建文件信息
* @param configId 文件配置编号
* @param name 文件名称
* @param url 文件地址
* @param file 文件
*/
function
createFile
(
configId
:
number
,
name
:
string
,
url
:
string
,
file
:
UploadRawFile
)
{
const
fileVo
=
{
configId
:
configId
,
path
:
name
,
// 移除预签名参数:参数只在上传时有用,查看时不需要
url
:
url
.
substring
(
0
,
url
.
indexOf
(
'?'
)),
name
:
file
.
name
,
type
:
file
.
type
,
size
:
file
.
size
}
FileApi
.
createFile
(
fileVo
)
return
fileVo
}
/**
* 生成文件名称(使用算法SHA256)
* @param file 要上传的文件
*/
async
function
generateFileName
(
file
:
UploadRawFile
)
{
// 读取文件内容
const
data
=
await
file
.
arrayBuffer
()
const
wordArray
=
CryptoJS
.
lib
.
WordArray
.
create
(
data
)
// 计算SHA256
const
sha256
=
CryptoJS
.
SHA256
(
wordArray
).
toString
()
// 拼接后缀
const
ext
=
file
.
name
.
substring
(
file
.
name
.
lastIndexOf
(
'.'
))
return
`
${
sha256
}${
ext
}
`
}
/**
* 上传类型
*/
enum
UPLOAD_TYPE
{
// 客户端直接上传(只支持S3服务)
CLIENT
=
'client'
,
// 客户端发送到后端上传
SERVER
=
'server'
}
src/views/infra/file/FileForm.vue
View file @
490bb901
...
...
@@ -3,16 +3,16 @@
<el-upload
ref=
"uploadRef"
v-model:file-list=
"fileList"
:action=
"url"
:action=
"u
ploadU
rl"
:auto-upload=
"false"
:data=
"data"
:disabled=
"formLoading"
:headers=
"uploadHeaders"
:limit=
"1"
:on-change=
"handleFileChange"
:on-error=
"submitFormError"
:on-exceed=
"handleExceed"
:on-success=
"submitFormSuccess"
:http-request=
"httpRequest"
accept=
".jpg, .png, .gif"
drag
>
...
...
@@ -31,7 +31,7 @@
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
{
getAccessToken
,
getTenantId
}
from
'@/utils/auth
'
import
{
useUpload
}
from
'@/components/UploadFile/src/useUpload
'
defineOptions
({
name
:
'InfraFileForm'
})
...
...
@@ -40,12 +40,12 @@ const message = useMessage() // 消息弹窗
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
formLoading
=
ref
(
false
)
// 表单的加载中
const
url
=
import
.
meta
.
env
.
VITE_UPLOAD_URL
const
uploadHeaders
=
ref
()
// 上传 Header 头
const
fileList
=
ref
([])
// 文件列表
const
data
=
ref
({
path
:
''
})
const
uploadRef
=
ref
()
const
{
uploadUrl
,
httpRequest
}
=
useUpload
()
/** 打开弹窗 */
const
open
=
async
()
=>
{
dialogVisible
.
value
=
true
...
...
@@ -64,11 +64,6 @@ const submitFileForm = () => {
message
.
error
(
'请上传文件'
)
return
}
// 提交请求
uploadHeaders
.
value
=
{
Authorization
:
'Bearer '
+
getAccessToken
(),
'tenant-id'
:
getTenantId
()
}
unref
(
uploadRef
)?.
submit
()
}
...
...
src/views/infra/file/index.vue
View file @
490bb901
...
...
@@ -70,8 +70,17 @@
preview-teleported
fit=
"cover"
/>
<el-link
v-else-if=
"row.type.includes('pdf')"
type=
"primary"
:href=
"row.url"
:underline=
"false"
target=
"_blank"
>
预览
</el-link>
<el-link
v-else
type=
"primary"
download
:href=
"row.url"
:underline=
"false"
target=
"_blank"
>
下载
</el-link>
<el-link
v-else-if=
"row.type.includes('pdf')"
type=
"primary"
:href=
"row.url"
:underline=
"false"
target=
"_blank"
>
预览
</el-link
>
<el-link
v-else
type=
"primary"
download
:href=
"row.url"
:underline=
"false"
target=
"_blank"
>
下载
</el-link
>
</
template
>
</el-table-column>
<el-table-column
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment