Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
corporate-culture-qd
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
王立鹏
corporate-culture-qd
Commits
f63235cf
Commit
f63235cf
authored
Nov 05, 2026
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat:分片上传改为通过OA接口获取NAS token,移除前端密钥并支持401刷新重试
parent
de08130a
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
61 additions
and
44 deletions
+61
-44
index.ts
src/api/common/index.ts
+60
-43
user.ts
src/stores/user.ts
+1
-1
No files found.
src/api/common/index.ts
View file @
f63235cf
import
axios
,
{
type
AxiosRequestConfig
}
from
'axios'
import
axios
,
{
type
AxiosRequestConfig
}
from
'axios'
import
CryptoJS
from
'crypto-js'
import
CryptoJS
from
'crypto-js'
import
{
useUserStore
}
from
'@/stores'
import
{
useUserStore
}
from
'@/stores'
import
service
from
'@/utils/request'
import
type
{
UploadFileResponseItem
,
ChunkCheckResponse
}
from
'./types'
import
type
{
UploadFileResponseItem
,
ChunkCheckResponse
}
from
'./types'
// 'https://oa.yswg.com.cn:8085'
// 'https://oa.yswg.com.cn:8085'
...
@@ -35,9 +36,6 @@ const OA_UPLOAD_COMMON_BASE_URL = 'http://47.112.96.71:8082'
...
@@ -35,9 +36,6 @@ const OA_UPLOAD_COMMON_BASE_URL = 'http://47.112.96.71:8082'
const
CHUNK_UPLOAD_THRESHOLD
=
1
*
1024
*
1024
const
CHUNK_UPLOAD_THRESHOLD
=
1
*
1024
*
1024
const
CHUNK_SIZE
=
5
*
1024
*
1024
const
CHUNK_SIZE
=
5
*
1024
*
1024
const
MAX_CONCURRENT_UPLOADS
=
3
const
MAX_CONCURRENT_UPLOADS
=
3
const
CHUNK_API_AUTH_SECRET
=
'4821-7395-1642-8053-2971-5604-9182-4307'
// nas 鉴权接口的超时时间 nas 挂掉时浏览器默认要等 20s 以上 这里主动掐断
const
NAS_AUTH_TIMEOUT
=
8000
// nas 连不上时统一对外抛这个文案 避免把 ERR_CONNECTION_REFUSED 这种原始错误甩给用户
// nas 连不上时统一对外抛这个文案 避免把 ERR_CONNECTION_REFUSED 这种原始错误甩给用户
export
const
UPLOAD_SERVICE_UNAVAILABLE_MSG
=
'视频上传服务暂时不可用,请稍后重试'
export
const
UPLOAD_SERVICE_UNAVAILABLE_MSG
=
'视频上传服务暂时不可用,请稍后重试'
...
@@ -134,6 +132,29 @@ function createChunks(file: File, chunkSize: number) {
...
@@ -134,6 +132,29 @@ function createChunks(file: File, chunkSize: number) {
return
chunks
return
chunks
}
}
/**
* nas 上传专用的 axios 实例
* 请求:每次随机选一个 nas 节点作为 baseURL 并带上 nas token
* 响应:401(token 过期/无效)时强制换新 token 后重试一次
*/
const
nasUploadService
=
axios
.
create
({})
nasUploadService
.
interceptors
.
request
.
use
(
async
(
config
)
=>
{
config
.
baseURL
=
resolveUploadBaseUrl
()
config
.
headers
.
set
(
'authorization'
,
await
ensureChunkApiAuthToken
())
return
config
})
nasUploadService
.
interceptors
.
response
.
use
(
undefined
,
async
(
error
)
=>
{
const
config
=
error
.
config
as
(
AxiosRequestConfig
&
{
_retried
?:
boolean
})
|
undefined
if
(
axios
.
isAxiosError
(
error
)
&&
error
.
response
?.
status
===
401
&&
config
&&
!
config
.
_retried
)
{
config
.
_retried
=
true
await
refreshChunkApiAuthToken
()
return
nasUploadService
.
request
(
config
)
}
return
Promise
.
reject
(
error
)
})
// 分片上传文件
// 分片上传文件
/**
/**
* 第 1 步:给文件生成一个 hash
* 第 1 步:给文件生成一个 hash
...
@@ -151,22 +172,23 @@ async function uploadFileByChunks(
...
@@ -151,22 +172,23 @@ async function uploadFileByChunks(
},
},
signal
:
AbortSignal
,
signal
:
AbortSignal
,
):
Promise
<
UploadFileResponseItem
>
{
):
Promise
<
UploadFileResponseItem
>
{
const
authToken
=
await
ensureChunkApiAuthToken
()
// 先确保有 token 没有的话在计算文件 hash 之前就失败 不用白算
await
ensureChunkApiAuthToken
()
const
{
onProgress
}
=
options
const
{
onProgress
}
=
options
const
fileHash
=
await
fileHashHeadTailMiddle
(
file
)
const
fileHash
=
await
fileHashHeadTailMiddle
(
file
)
const
fileSuffix
=
file
.
name
.
includes
(
'.'
)
?
file
.
name
.
slice
(
file
.
name
.
lastIndexOf
(
'.'
))
:
''
const
fileSuffix
=
file
.
name
.
includes
(
'.'
)
?
file
.
name
.
slice
(
file
.
name
.
lastIndexOf
(
'.'
))
:
''
// 上传到服务端用的文件名 带上 hash 防止重名覆盖
// 上传到服务端用的文件名 带上 hash 防止重名覆盖
const
uploadFileName
=
buildUniqueFileName
(
file
.
name
,
fileHash
)
const
uploadFileName
=
buildUniqueFileName
(
file
.
name
,
fileHash
)
const
checkResponse
=
await
axios
.
post
<
{
data
:
ChunkCheckResponse
}
>
(
const
checkResponse
=
await
nasUploadService
.
post
<
{
data
:
ChunkCheckResponse
}
>
(
`
${
resolveUploadBaseUrl
()}
/mobiles/file-upload/check`
,
'/mobiles/file-upload/check'
,
{
{
hash
:
fileHash
,
hash
:
fileHash
,
fileName
:
uploadFileName
,
fileName
:
uploadFileName
,
fileSize
:
file
.
size
,
fileSize
:
file
.
size
,
fileSuffx
:
fileSuffix
,
fileSuffx
:
fileSuffix
,
},
},
{
signal
,
headers
:
{
authorization
:
authToken
}
},
{
signal
},
)
)
// 秒传服务检查文件是否存在
// 秒传服务检查文件是否存在
...
@@ -208,12 +230,9 @@ async function uploadFileByChunks(
...
@@ -208,12 +230,9 @@ async function uploadFileByChunks(
formData
.
append
(
'filePart'
,
chunk
,
file
.
name
)
formData
.
append
(
'filePart'
,
chunk
,
file
.
name
)
formData
.
append
(
'chunkNumber'
,
String
(
index
))
formData
.
append
(
'chunkNumber'
,
String
(
index
))
await
axios
.
post
(
`
${
resolveUploadBaseUrl
()}
/mobiles/file-upload/chunk`
,
formData
,
{
await
nasUploadService
.
post
(
'/mobiles/file-upload/chunk'
,
formData
,
{
signal
,
signal
,
headers
:
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
},
'Content-Type'
:
'multipart/form-data'
,
authorization
:
authToken
,
},
})
})
uploadedSet
.
add
(
index
)
uploadedSet
.
add
(
index
)
...
@@ -247,15 +266,12 @@ async function uploadFileByChunks(
...
@@ -247,15 +266,12 @@ async function uploadFileByChunks(
finishFormData
.
set
(
'chunkTotalCount'
,
String
(
totalChunks
))
finishFormData
.
set
(
'chunkTotalCount'
,
String
(
totalChunks
))
// 上传完成 通知后端文件上传完成
// 上传完成 通知后端文件上传完成
const
finishResponse
=
await
axios
.
post
<
{
data
:
{
fileUrl
:
string
}
}
>
(
const
finishResponse
=
await
nasUploadService
.
post
<
{
data
:
{
fileUrl
:
string
}
}
>
(
`
${
resolveUploadBaseUrl
()}
/mobiles/file-upload/finish`
,
'/mobiles/file-upload/finish'
,
finishFormData
,
finishFormData
,
{
{
signal
,
signal
,
headers
:
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
},
'Content-Type'
:
'multipart/form-data'
,
authorization
:
authToken
,
},
},
},
)
)
...
@@ -316,29 +332,33 @@ export const uploadFile = (
...
@@ -316,29 +332,33 @@ export const uploadFile = (
}
}
}
}
export
const
getTimestamp
=
async
()
=>
{
/**
const
{
* 获取 nas 上传接口的 token(由 OA 后端代换,前端不再持有密钥)
data
:
{
data
:
timestamp
},
* @param forceRefresh 上传服务返回 401/Token无效 时传 true,强制换新 token
}
=
await
axios
.
get
(
`
${
resolveUploadBaseUrl
()}
/api/auth/getTime`
,
{
timeout
:
NAS_AUTH_TIMEOUT
})
*/
return
timestamp
export
const
getChunkApiAuthToken
=
async
(
forceRefresh
=
false
)
=>
{
const
res
=
await
service
.
request
<
{
token
:
string
}
>
({
url
:
'/nasUpload/getToken'
,
method
:
'POST'
,
data
:
{
forceRefresh
},
})
return
res
.
data
.
token
}
}
export
const
getChunkApiAuthToken
=
async
(
weChatId
:
string
)
=>
{
// 多个分片并发遇到 401 时 共用同一次刷新
const
timestamp
=
await
getTimestamp
()
let
refreshTokenPromise
:
Promise
<
string
>
|
null
=
null
const
refreshChunkApiAuthToken
=
()
=>
{
const
rawStr
=
CHUNK_API_AUTH_SECRET
+
timestamp
if
(
!
refreshTokenPromise
)
{
const
{
refreshTokenPromise
=
getChunkApiAuthToken
(
true
)
data
:
{
data
},
.
then
((
token
)
=>
{
}
=
await
axios
.
post
(
useUserStore
().
setChunkApiAuthToken
(
token
)
`
${
resolveUploadBaseUrl
()}
/api/auth/getToken`
,
return
token
{
})
timestamp
,
.
finally
(()
=>
{
weChatId
,
refreshTokenPromise
=
null
secret
:
CryptoJS
.
MD5
(
rawStr
).
toString
(),
})
},
}
{
timeout
:
NAS_AUTH_TIMEOUT
},
return
refreshTokenPromise
)
return
data
.
token
as
string
}
}
/**
/**
...
@@ -350,11 +370,8 @@ const ensureChunkApiAuthToken = async () => {
...
@@ -350,11 +370,8 @@ const ensureChunkApiAuthToken = async () => {
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
if
(
userStore
.
chunkApiAuthToken
)
return
userStore
.
chunkApiAuthToken
if
(
userStore
.
chunkApiAuthToken
)
return
userStore
.
chunkApiAuthToken
const
account
=
userStore
.
userInfo
?.
account
if
(
!
account
)
throw
new
Error
(
UPLOAD_SERVICE_UNAVAILABLE_MSG
)
try
{
try
{
const
token
=
await
getChunkApiAuthToken
(
account
)
const
token
=
await
getChunkApiAuthToken
()
userStore
.
setChunkApiAuthToken
(
token
)
userStore
.
setChunkApiAuthToken
(
token
)
return
token
return
token
}
catch
(
error
)
{
}
catch
(
error
)
{
...
...
src/stores/user.ts
View file @
f63235cf
...
@@ -49,7 +49,7 @@ export const useUserStore = defineStore('user', () => {
...
@@ -49,7 +49,7 @@ export const useUserStore = defineStore('user', () => {
// 所以不 await 直接放后台跑 让首页尽快出来 nas 慢或者挂了也不会拖住登录
// 所以不 await 直接放后台跑 让首页尽快出来 nas 慢或者挂了也不会拖住登录
// 这里的 catch 不能省 不然 nas 挂掉时会变成未处理的 promise rejection
// 这里的 catch 不能省 不然 nas 挂掉时会变成未处理的 promise rejection
// 万一没拿到 等真正要分片上传的时候会再补一次(见 ensureChunkApiAuthToken)
// 万一没拿到 等真正要分片上传的时候会再补一次(见 ensureChunkApiAuthToken)
getChunkApiAuthToken
(
data
.
account
)
getChunkApiAuthToken
()
.
then
(
setChunkApiAuthToken
)
.
then
(
setChunkApiAuthToken
)
.
catch
((
error
)
=>
{
.
catch
((
error
)
=>
{
console
.
error
(
'获取分片上传 token 失败 不影响登录'
,
error
)
console
.
error
(
'获取分片上传 token 失败 不影响登录'
,
error
)
...
...
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