Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
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
Unverified
Commit
e11aa7a8
authored
Jul 09, 2026
by
light5980
Committed by
GitHub
Jul 09, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix certification (#7282)
* fix certification * fix:certification limit * fix:certification key gen
parent
e5ced8a4
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
212 additions
and
94 deletions
+212
-94
packages/global/openapi/support/user/team/enterpriseAuth/api.ts
+3
-13
packages/global/test/openapi/support/user/team/enterpriseAuth/api.test.ts
+46
-3
packages/service/common/middle/tracks/utils.ts
+0
-1
packages/service/common/system/frequencyLimit/redisFixedWindow.ts
+23
-0
packages/service/test/common/system/frequencyLimit/redisFixedWindow.test.ts
+36
-0
pro
+1
-1
projects/app/src/pageComponents/account/team/EnterpriseAuth/Modal.tsx
+8
-3
projects/app/src/pageComponents/account/team/EnterpriseAuth/index.tsx
+7
-2
projects/app/src/pageComponents/account/team/EnterpriseAuth/useEnterpriseAuthFormFlow.ts
+21
-26
projects/app/src/pageComponents/account/team/EnterpriseAuth/utils.ts
+15
-18
projects/app/test/pageComponents/account/team/EnterpriseAuth/utils.test.ts
+7
-19
test/mocks/common/redis.ts
+45
-8
No files found.
packages/global/openapi/support/user/team/enterpriseAuth/api.ts
View file @
e11aa7a8
import
z
from
'zod'
;
import
z
from
'zod'
;
import
{
import
{
EnterpriseAuthAmountMaxErrorTimes
,
EnterpriseAuthAmountMaxErrorTimes
,
EnterpriseAuthMaxTimes
,
TeamEnterpriseAuthStatusSchema
,
TeamEnterpriseAuthStatusSchema
,
TeamEnterpriseAuthTaskStatusSchema
TeamEnterpriseAuthTaskStatusSchema
}
from
'../../../../../support/user/team/enterpriseAuth/constant'
;
}
from
'../../../../../support/user/team/enterpriseAuth/constant'
;
...
@@ -21,7 +20,6 @@ import {
...
@@ -21,7 +20,6 @@ import {
* ============================================================================ */
* ============================================================================ */
export
const
EnterpriseAuthLightTaskSchema
=
z
.
object
({
export
const
EnterpriseAuthLightTaskSchema
=
z
.
object
({
taskId
:
z
.
string
().
meta
({
description
:
'认证任务 ID'
}),
status
:
TeamEnterpriseAuthTaskStatusSchema
.
meta
({
description
:
'当前任务状态'
}),
status
:
TeamEnterpriseAuthTaskStatusSchema
.
meta
({
description
:
'当前任务状态'
}),
amountErrorTimes
:
z
.
number
().
int
().
meta
({
amountErrorTimes
:
z
.
number
().
int
().
meta
({
description
:
'当前任务金额填写错误次数'
,
description
:
'当前任务金额填写错误次数'
,
...
@@ -32,11 +30,9 @@ export const EnterpriseAuthLightTaskSchema = z.object({
...
@@ -32,11 +30,9 @@ export const EnterpriseAuthLightTaskSchema = z.object({
export
const
GetEnterpriseAuthStatusResponseSchema
=
z
.
object
({
export
const
GetEnterpriseAuthStatusResponseSchema
=
z
.
object
({
enabled
:
z
.
boolean
().
meta
({
description
:
'企业认证入口是否开启'
}),
enabled
:
z
.
boolean
().
meta
({
description
:
'企业认证入口是否开启'
}),
status
:
TeamEnterpriseAuthStatusSchema
.
optional
().
meta
({
description
:
'团队认证状态'
}),
status
:
TeamEnterpriseAuthStatusSchema
.
optional
().
meta
({
description
:
'团队认证状态'
}),
usedTimes
:
z
hasRemainingAuthTimes
:
z
.
boolean
().
optional
().
meta
({
.
number
()
description
:
'是否还有可发起新企业认证的次数;已有金额验证任务仍可继续完成'
.
int
()
}),
.
optional
()
.
meta
({
description
:
`已使用认证次数,最多
${
EnterpriseAuthMaxTimes
}
次`
,
example
:
0
}),
canManage
:
z
.
boolean
().
optional
().
meta
({
description
:
'当前成员是否可管理企业认证'
}),
canManage
:
z
.
boolean
().
optional
().
meta
({
description
:
'当前成员是否可管理企业认证'
}),
verifiedEnterpriseName
:
z
.
string
().
optional
().
meta
({
description
:
'认证通过企业名称'
}),
verifiedEnterpriseName
:
z
.
string
().
optional
().
meta
({
description
:
'认证通过企业名称'
}),
currentTask
:
EnterpriseAuthLightTaskSchema
.
optional
().
meta
({
currentTask
:
EnterpriseAuthLightTaskSchema
.
optional
().
meta
({
...
@@ -58,7 +54,6 @@ export type GetEnterpriseAuthStatusResponseType = z.infer<
...
@@ -58,7 +54,6 @@ export type GetEnterpriseAuthStatusResponseType = z.infer<
* ============================================================================ */
* ============================================================================ */
export
const
GetEnterpriseAuthCurrentTaskDetailResponseSchema
=
z
.
object
({
export
const
GetEnterpriseAuthCurrentTaskDetailResponseSchema
=
z
.
object
({
taskId
:
z
.
string
().
meta
({
description
:
'认证任务 ID'
}),
status
:
TeamEnterpriseAuthTaskStatusSchema
.
meta
({
description
:
'当前任务状态'
}),
status
:
TeamEnterpriseAuthTaskStatusSchema
.
meta
({
description
:
'当前任务状态'
}),
enterpriseName
:
z
.
string
().
meta
({
description
:
'企业名称'
}),
enterpriseName
:
z
.
string
().
meta
({
description
:
'企业名称'
}),
unifiedCreditCode
:
z
.
string
().
meta
({
description
:
'统一社会信用代码'
}),
unifiedCreditCode
:
z
.
string
().
meta
({
description
:
'统一社会信用代码'
}),
...
@@ -157,16 +152,11 @@ export const StartEnterpriseAuthResponseSchema = z.object({
...
@@ -157,16 +152,11 @@ export const StartEnterpriseAuthResponseSchema = z.object({
currentTask
:
EnterpriseAuthLightTaskSchema
.
optional
().
meta
({
currentTask
:
EnterpriseAuthLightTaskSchema
.
optional
().
meta
({
description
:
'未完成认证任务;仅 pending_amount/amount_failed 可进入金额验证页'
description
:
'未完成认证任务;仅 pending_amount/amount_failed 可进入金额验证页'
}),
}),
usedTimes
:
z
.
number
()
.
int
()
.
meta
({
description
:
`已使用认证次数,最多
${
EnterpriseAuthMaxTimes
}
次`
}),
message
:
z
.
string
().
optional
().
meta
({
description
:
'流程提示'
})
message
:
z
.
string
().
optional
().
meta
({
description
:
'流程提示'
})
});
});
export
type
StartEnterpriseAuthResponseType
=
z
.
infer
<
typeof
StartEnterpriseAuthResponseSchema
>
;
export
type
StartEnterpriseAuthResponseType
=
z
.
infer
<
typeof
StartEnterpriseAuthResponseSchema
>
;
export
const
VerifyEnterpriseAuthAmountBodySchema
=
z
.
object
({
export
const
VerifyEnterpriseAuthAmountBodySchema
=
z
.
object
({
taskId
:
z
.
string
().
min
(
1
).
meta
({
description
:
'认证任务 ID'
}),
amountCent
:
VerifyEnterpriseAuthAmountCentSchema
.
meta
({
amountCent
:
VerifyEnterpriseAuthAmountCentSchema
.
meta
({
description
:
'用户填写的到账金额,单位为分'
,
description
:
'用户填写的到账金额,单位为分'
,
example
:
123
example
:
123
...
...
packages/global/test/openapi/support/user/team/enterpriseAuth/api.test.ts
View file @
e11aa7a8
import
{
describe
,
expect
,
it
}
from
'vitest'
;
import
{
describe
,
expect
,
it
}
from
'vitest'
;
import
{
import
{
GetEnterpriseAuthStatusResponseSchema
,
StartEnterpriseAuthBodySchema
,
StartEnterpriseAuthBodySchema
,
StartEnterpriseAuthResponseSchema
,
VerifyEnterpriseAuthAmountBodySchema
VerifyEnterpriseAuthAmountBodySchema
}
from
'../../../../../../openapi/support/user/team/enterpriseAuth/api'
;
}
from
'../../../../../../openapi/support/user/team/enterpriseAuth/api'
;
import
{
TeamEnterpriseAuthStatusEnum
,
TeamEnterpriseAuthTaskStatusEnum
}
from
'../../../../../../support/user/team/enterpriseAuth/constant'
;
describe
(
'VerifyEnterpriseAuthAmountBodySchema'
,
()
=>
{
describe
(
'VerifyEnterpriseAuthAmountBodySchema'
,
()
=>
{
it
(
'只接受严格正整数金额,避免无效金额消耗验证次数'
,
()
=>
{
it
(
'只接受严格正整数金额,避免无效金额消耗验证次数'
,
()
=>
{
expect
(
expect
(
VerifyEnterpriseAuthAmountBodySchema
.
parse
({
VerifyEnterpriseAuthAmountBodySchema
.
parse
({
taskId
:
'task-1'
,
amountCent
:
123
amountCent
:
123
})
})
).
toEqual
({
).
toEqual
({
taskId
:
'task-1'
,
amountCent
:
123
amountCent
:
123
});
});
[
0
,
-
1
,
1.5
,
''
,
'123'
,
null
,
undefined
,
false
].
forEach
((
amountCent
)
=>
{
[
0
,
-
1
,
1.5
,
''
,
'123'
,
null
,
undefined
,
false
].
forEach
((
amountCent
)
=>
{
expect
(
expect
(
VerifyEnterpriseAuthAmountBodySchema
.
safeParse
({
VerifyEnterpriseAuthAmountBodySchema
.
safeParse
({
taskId
:
'task-1'
,
amountCent
amountCent
}).
success
}).
success
).
toBe
(
false
);
).
toBe
(
false
);
...
@@ -75,3 +78,43 @@ describe('StartEnterpriseAuthBodySchema', () => {
...
@@ -75,3 +78,43 @@ describe('StartEnterpriseAuthBodySchema', () => {
});
});
});
});
});
});
describe
(
'EnterpriseAuth response schemas'
,
()
=>
{
it
(
'状态接口响应不再暴露 usedTimes'
,
()
=>
{
const
result
=
GetEnterpriseAuthStatusResponseSchema
.
parse
({
enabled
:
true
,
status
:
TeamEnterpriseAuthStatusEnum
.
unverified
,
usedTimes
:
3
,
hasRemainingAuthTimes
:
false
,
canManage
:
true
});
expect
(
result
).
toEqual
({
enabled
:
true
,
status
:
TeamEnterpriseAuthStatusEnum
.
unverified
,
hasRemainingAuthTimes
:
false
,
canManage
:
true
});
});
it
(
'发起认证响应不再暴露 usedTimes'
,
()
=>
{
const
result
=
StartEnterpriseAuthResponseSchema
.
parse
({
status
:
TeamEnterpriseAuthStatusEnum
.
verifying
,
currentTask
:
{
status
:
TeamEnterpriseAuthTaskStatusEnum
.
pending_amount
,
amountErrorTimes
:
0
},
usedTimes
:
1
,
message
:
'已成功打款,请确认打款金额'
});
expect
(
result
).
toEqual
({
status
:
TeamEnterpriseAuthStatusEnum
.
verifying
,
currentTask
:
{
status
:
TeamEnterpriseAuthTaskStatusEnum
.
pending_amount
,
amountErrorTimes
:
0
},
message
:
'已成功打款,请确认打款金额'
});
});
});
packages/service/common/middle/tracks/utils.ts
View file @
e11aa7a8
...
@@ -171,7 +171,6 @@ export const pushTrack = {
...
@@ -171,7 +171,6 @@ export const pushTrack = {
status
?:
`
${
TeamEnterpriseAuthStatusEnum
}
`
;
status
?:
`
${
TeamEnterpriseAuthStatusEnum
}
`
;
taskStatus
?:
`
${
TeamEnterpriseAuthTaskStatusEnum
}
`
;
taskStatus
?:
`
${
TeamEnterpriseAuthTaskStatusEnum
}
`
;
errorCode
?:
string
;
errorCode
?:
string
;
usedTimes
?:
number
;
hasCurrentTask
?:
boolean
;
hasCurrentTask
?:
boolean
;
}
}
)
=>
{
)
=>
{
...
...
packages/service/common/system/frequencyLimit/redisFixedWindow.ts
0 → 100644
View file @
e11aa7a8
import
{
getGlobalRedisConnection
}
from
'../../redis'
;
/**
* 基于 Redis 固定窗口的轻量 QPM 限流。
*
* 该 helper 只负责窗口内计数并返回是否允许继续执行,不绑定任何业务错误码。
* 调用方需要根据自身语义决定超限后的错误响应。
*/
export
const
checkFixedWindowQpmLimit
=
async
({
key
,
limit
,
seconds
=
60
}:
{
key
:
string
;
limit
:
number
;
seconds
?:
number
;
})
=>
{
const
redis
=
getGlobalRedisConnection
();
const
result
=
await
redis
.
multi
().
incr
(
key
).
expire
(
key
,
seconds
,
'NX'
).
exec
();
const
currentCount
=
Number
(
result
?.[
0
]?.[
1
]
??
0
);
return
currentCount
<=
limit
;
};
packages/service/test/common/system/frequencyLimit/redisFixedWindow.test.ts
0 → 100644
View file @
e11aa7a8
import
{
beforeEach
,
describe
,
expect
,
it
}
from
'vitest'
;
import
{
checkFixedWindowQpmLimit
}
from
'@fastgpt/service/common/system/frequencyLimit/redisFixedWindow'
;
import
{
getGlobalRedisConnection
}
from
'@fastgpt/service/common/redis'
;
describe
(
'checkFixedWindowQpmLimit'
,
()
=>
{
beforeEach
(
async
()
=>
{
await
getGlobalRedisConnection
().
flushdb
();
});
it
(
'同一个 key 在固定窗口内超过限制后返回 false'
,
async
()
=>
{
await
expect
(
checkFixedWindowQpmLimit
({
key
:
'enterprise-auth:start:team:t1'
,
limit
:
3
})
).
resolves
.
toBe
(
true
);
await
expect
(
checkFixedWindowQpmLimit
({
key
:
'enterprise-auth:start:team:t1'
,
limit
:
3
})
).
resolves
.
toBe
(
true
);
await
expect
(
checkFixedWindowQpmLimit
({
key
:
'enterprise-auth:start:team:t1'
,
limit
:
3
})
).
resolves
.
toBe
(
true
);
await
expect
(
checkFixedWindowQpmLimit
({
key
:
'enterprise-auth:start:team:t1'
,
limit
:
3
})
).
resolves
.
toBe
(
false
);
});
it
(
'不同 key 独立计数'
,
async
()
=>
{
await
expect
(
checkFixedWindowQpmLimit
({
key
:
'enterprise-auth:start:team:t1'
,
limit
:
1
})
).
resolves
.
toBe
(
true
);
await
expect
(
checkFixedWindowQpmLimit
({
key
:
'enterprise-auth:start:team:t1'
,
limit
:
1
})
).
resolves
.
toBe
(
false
);
await
expect
(
checkFixedWindowQpmLimit
({
key
:
'enterprise-auth:start:team:t2'
,
limit
:
1
})
).
resolves
.
toBe
(
true
);
});
});
pro
@
63abbb63
Subproject commit
c37a34e9ac142a168af29b5dfcece1b12cccdcca
Subproject commit
63abbb63af6502569932de2a33ab6ed0eac2cd45
projects/app/src/pageComponents/account/team/EnterpriseAuth/Modal.tsx
View file @
e11aa7a8
...
@@ -11,18 +11,23 @@ import { enterpriseAuthFooterButtonStyles } from './shared';
...
@@ -11,18 +11,23 @@ import { enterpriseAuthFooterButtonStyles } from './shared';
type
EnterpriseAuthModalProps
=
{
type
EnterpriseAuthModalProps
=
{
defaultStatus
:
GetEnterpriseAuthStatusResponseType
;
defaultStatus
:
GetEnterpriseAuthStatusResponseType
;
onClose
:
()
=>
void
;
onClose
:
()
=>
void
;
onNoRemainingTimes
:
()
=>
void
;
onSuccess
:
()
=>
void
;
onSuccess
:
()
=>
void
;
};
};
const
EnterpriseAuthModal
=
({
defaultStatus
,
onClose
,
onSuccess
}:
EnterpriseAuthModalProps
)
=>
{
const
EnterpriseAuthModal
=
({
defaultStatus
,
onClose
,
onNoRemainingTimes
,
onSuccess
}:
EnterpriseAuthModalProps
)
=>
{
const
flow
=
useEnterpriseAuthFormFlow
({
const
flow
=
useEnterpriseAuthFormFlow
({
defaultStatus
,
defaultStatus
,
onClose
,
onClose
,
onNoRemainingTimes
,
onSuccess
onSuccess
});
});
if
(
flow
.
shouldBlockEnterpriseAuthForm
)
return
null
;
const
remainingAmountVerifyTimes
=
Math
.
max
(
const
remainingAmountVerifyTimes
=
Math
.
max
(
EnterpriseAuthAmountMaxErrorTimes
-
EnterpriseAuthAmountMaxErrorTimes
-
(
flow
.
taskDetail
?.
amountErrorTimes
??
defaultStatus
.
currentTask
?.
amountErrorTimes
??
0
),
(
flow
.
taskDetail
?.
amountErrorTimes
??
defaultStatus
.
currentTask
?.
amountErrorTimes
??
0
),
...
...
projects/app/src/pageComponents/account/team/EnterpriseAuth/index.tsx
View file @
e11aa7a8
...
@@ -120,11 +120,11 @@ const EnterpriseAuthStatusRow = ({
...
@@ -120,11 +120,11 @@ const EnterpriseAuthStatusRow = ({
}),
}),
[
data
?.
currentTask
,
data
?.
status
,
data
?.
verifiedEnterpriseName
,
t
]
[
data
?.
currentTask
,
data
?.
status
,
data
?.
verifiedEnterpriseName
,
t
]
);
);
const
canOpenCurrentTask
=
canOpenEnterpriseAuthAmountStep
(
data
?.
currentTask
?.
status
);
const
needContactBusiness
=
shouldShowEnterpriseAuthContactBusinessModal
({
const
needContactBusiness
=
shouldShowEnterpriseAuthContactBusinessModal
({
usedTimes
:
data
?.
used
Times
,
hasRemainingAuthTimes
:
data
?.
hasRemainingAuth
Times
,
hasCurrentTask
:
!!
data
?.
currentTask
hasCurrentTask
:
!!
data
?.
currentTask
});
});
const
canOpenCurrentTask
=
canOpenEnterpriseAuthAmountStep
(
data
?.
currentTask
?.
status
);
const
hasOtherMemberProcessingTask
=
const
hasOtherMemberProcessingTask
=
data
?.
status
===
TeamEnterpriseAuthStatusEnum
.
verifying
&&
!
data
?.
currentTask
;
data
?.
status
===
TeamEnterpriseAuthStatusEnum
.
verifying
&&
!
data
?.
currentTask
;
const
canManageCurrentEnterpriseAuth
=
canManageEnterpriseAuth
({
const
canManageCurrentEnterpriseAuth
=
canManageEnterpriseAuth
({
...
@@ -287,6 +287,11 @@ const EnterpriseAuthStatusRow = ({
...
@@ -287,6 +287,11 @@ const EnterpriseAuthStatusRow = ({
<
EnterpriseAuthModal
<
EnterpriseAuthModal
defaultStatus=
{
data
}
defaultStatus=
{
data
}
onClose=
{
onClose
}
onClose=
{
onClose
}
onNoRemainingTimes=
{
()
=>
{
onClose
();
refreshStatus
();
onOpenContactBusiness
();
}
}
onSuccess=
{
()
=>
{
onSuccess=
{
()
=>
{
refreshStatus
();
refreshStatus
();
initUserInfo
();
initUserInfo
();
...
...
projects/app/src/pageComponents/account/team/EnterpriseAuth/useEnterpriseAuthFormFlow.ts
View file @
e11aa7a8
...
@@ -16,11 +16,7 @@ import {
...
@@ -16,11 +16,7 @@ import {
startEnterpriseAuth
,
startEnterpriseAuth
,
verifyEnterpriseAuthAmount
verifyEnterpriseAuthAmount
}
from
'@/web/support/user/team/enterpriseAuth/api'
;
}
from
'@/web/support/user/team/enterpriseAuth/api'
;
import
{
import
{
canOpenEnterpriseAuthAmountStep
,
shouldShowEnterpriseAuthAmountError
}
from
'./utils'
;
canOpenEnterpriseAuthAmountStep
,
shouldShowEnterpriseAuthAmountError
,
shouldShowEnterpriseAuthContactBusinessModal
}
from
'./utils'
;
import
{
import
{
formatEnterpriseAuthBankOptions
,
formatEnterpriseAuthBankOptions
,
getErrorCode
,
getErrorCode
,
...
@@ -31,20 +27,18 @@ import {
...
@@ -31,20 +27,18 @@ import {
type
UseEnterpriseAuthFormFlowProps
=
{
type
UseEnterpriseAuthFormFlowProps
=
{
defaultStatus
:
GetEnterpriseAuthStatusResponseType
;
defaultStatus
:
GetEnterpriseAuthStatusResponseType
;
onClose
:
()
=>
void
;
onClose
:
()
=>
void
;
onNoRemainingTimes
:
()
=>
void
;
onSuccess
:
()
=>
void
;
onSuccess
:
()
=>
void
;
};
};
export
const
useEnterpriseAuthFormFlow
=
({
export
const
useEnterpriseAuthFormFlow
=
({
defaultStatus
,
defaultStatus
,
onClose
,
onClose
,
onNoRemainingTimes
,
onSuccess
onSuccess
}:
UseEnterpriseAuthFormFlowProps
)
=>
{
}:
UseEnterpriseAuthFormFlowProps
)
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
{
toast
}
=
useToast
();
const
{
toast
}
=
useToast
();
const
shouldBlockEnterpriseAuthForm
=
shouldShowEnterpriseAuthContactBusinessModal
({
usedTimes
:
defaultStatus
.
usedTimes
,
hasCurrentTask
:
!!
defaultStatus
.
currentTask
});
const
canOpenInitialAmountStep
=
canOpenEnterpriseAuthAmountStep
(
const
canOpenInitialAmountStep
=
canOpenEnterpriseAuthAmountStep
(
defaultStatus
.
currentTask
?.
status
defaultStatus
.
currentTask
?.
status
);
);
...
@@ -82,7 +76,7 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -82,7 +76,7 @@ export const useEnterpriseAuthFormFlow = ({
run
:
reloadBanks
run
:
reloadBanks
}
=
useRequest
(
getEnterpriseAuthBanks
,
{
}
=
useRequest
(
getEnterpriseAuthBanks
,
{
manual
:
false
,
manual
:
false
,
ready
:
step
===
'form'
&&
!
shouldBlockEnterpriseAuthForm
,
ready
:
step
===
'form'
,
errorToast
:
t
(
'account_team:enterprise_auth_bank_load_failed'
)
errorToast
:
t
(
'account_team:enterprise_auth_bank_load_failed'
)
});
});
...
@@ -98,7 +92,7 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -98,7 +92,7 @@ export const useEnterpriseAuthFormFlow = ({
});
});
const
{
runAsync
:
onStart
,
loading
:
starting
}
=
useRequest
(
startEnterpriseAuth
,
{
const
{
runAsync
:
onStart
,
loading
:
starting
}
=
useRequest
(
startEnterpriseAuth
,
{
errorToast
:
t
(
'account_team:enterprise_auth_submit_failed'
)
errorToast
:
''
});
});
const
{
runAsync
:
onVerify
,
loading
:
verifying
}
=
useRequest
(
verifyEnterpriseAuthAmount
,
{
const
{
runAsync
:
onVerify
,
loading
:
verifying
}
=
useRequest
(
verifyEnterpriseAuthAmount
,
{
errorToast
:
''
errorToast
:
''
...
@@ -111,7 +105,7 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -111,7 +105,7 @@ export const useEnterpriseAuthFormFlow = ({
const
hasBankLoadError
=
!!
bankLoadError
&&
!
bankOptions
.
length
;
const
hasBankLoadError
=
!!
bankLoadError
&&
!
bankOptions
.
length
;
const
isBankLoading
=
loadingBanks
;
const
isBankLoading
=
loadingBanks
;
const
amountYuanValue
=
useWatch
({
control
:
amountForm
.
control
,
name
:
'amountYuan'
});
const
amountYuanValue
=
useWatch
({
control
:
amountForm
.
control
,
name
:
'amountYuan'
});
const
hasLoadedTaskDetail
=
!!
taskDetail
?.
taskId
;
const
hasLoadedTaskDetail
=
!!
taskDetail
;
const
canSubmitAmount
=
const
canSubmitAmount
=
hasLoadedTaskDetail
&&
hasLoadedTaskDetail
&&
parseEnterpriseAuthAmountCent
(
String
(
amountYuanValue
??
''
))
!==
undefined
;
parseEnterpriseAuthAmountCent
(
String
(
amountYuanValue
??
''
))
!==
undefined
;
...
@@ -120,15 +114,6 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -120,15 +114,6 @@ export const useEnterpriseAuthFormFlow = ({
showCurrentSubmitError
:
showAmountError
showCurrentSubmitError
:
showAmountError
});
});
/**
* 认证次数耗尽且没有待验证任务时,认证表单本身也不应展示。
* 外层入口已做拦截,这里作为弹窗边界的兜底保护,避免旧状态或自动打开误触发。
*/
useEffect
(()
=>
{
if
(
!
shouldBlockEnterpriseAuthForm
)
return
;
onClose
();
},
[
onClose
,
shouldBlockEnterpriseAuthForm
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
defaultStatus
.
currentTask
||
canOpenInitialAmountStep
)
return
;
if
(
!
defaultStatus
.
currentTask
||
canOpenInitialAmountStep
)
return
;
onClose
();
onClose
();
...
@@ -136,7 +121,19 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -136,7 +121,19 @@ export const useEnterpriseAuthFormFlow = ({
const
handleStart
=
useCallback
(
const
handleStart
=
useCallback
(
async
(
data
:
StartEnterpriseAuthBodyType
)
=>
{
async
(
data
:
StartEnterpriseAuthBodyType
)
=>
{
const
result
=
await
onStart
(
data
);
const
result
=
await
onStart
(
data
).
catch
((
error
)
=>
{
if
(
getErrorCode
(
error
)
===
EnterpriseAuthErrEnum
.
noRemainingTimes
)
{
onNoRemainingTimes
();
return
;
}
toast
({
title
:
t
(
getErrText
(
error
,
t
(
'account_team:enterprise_auth_submit_failed'
))
as
any
),
status
:
'error'
});
});
if
(
!
result
)
return
;
onSuccess
();
onSuccess
();
if
(
canOpenEnterpriseAuthAmountStep
(
result
.
currentTask
?.
status
))
{
if
(
canOpenEnterpriseAuthAmountStep
(
result
.
currentTask
?.
status
))
{
await
loadTaskDetail
();
await
loadTaskDetail
();
...
@@ -151,7 +148,7 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -151,7 +148,7 @@ export const useEnterpriseAuthFormFlow = ({
});
});
onClose
();
onClose
();
},
},
[
loadTaskDetail
,
onClose
,
onStart
,
onSuccess
,
t
,
toast
]
[
loadTaskDetail
,
onClose
,
on
NoRemainingTimes
,
on
Start
,
onSuccess
,
t
,
toast
]
);
);
/**
/**
...
@@ -169,7 +166,7 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -169,7 +166,7 @@ export const useEnterpriseAuthFormFlow = ({
const
handleVerify
=
useCallback
(
const
handleVerify
=
useCallback
(
async
({
amountYuan
}:
AmountFormType
)
=>
{
async
({
amountYuan
}:
AmountFormType
)
=>
{
if
(
!
taskDetail
?.
taskId
)
{
if
(
!
taskDetail
)
{
toast
({
toast
({
title
:
t
(
'account_team:enterprise_auth_task_load_failed'
),
title
:
t
(
'account_team:enterprise_auth_task_load_failed'
),
status
:
'warning'
status
:
'warning'
...
@@ -188,7 +185,6 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -188,7 +185,6 @@ export const useEnterpriseAuthFormFlow = ({
try
{
try
{
await
onVerify
({
await
onVerify
({
taskId
:
taskDetail
.
taskId
,
amountCent
amountCent
});
});
toast
({
toast
({
...
@@ -263,7 +259,6 @@ export const useEnterpriseAuthFormFlow = ({
...
@@ -263,7 +259,6 @@ export const useEnterpriseAuthFormFlow = ({
hasLoadedTaskDetail
,
hasLoadedTaskDetail
,
canSubmitAmount
,
canSubmitAmount
,
shouldShowAmountError
,
shouldShowAmountError
,
shouldBlockEnterpriseAuthForm
,
setShowAmountError
,
setShowAmountError
,
handleStart
,
handleStart
,
handleStartClick
,
handleStartClick
,
...
...
projects/app/src/pageComponents/account/team/EnterpriseAuth/utils.ts
View file @
e11aa7a8
import
{
import
{
TeamEnterpriseAuthTaskStatusEnum
}
from
'@fastgpt/global/support/user/team/enterpriseAuth/constant'
;
EnterpriseAuthMaxTimes
,
TeamEnterpriseAuthTaskStatusEnum
}
from
'@fastgpt/global/support/user/team/enterpriseAuth/constant'
;
export
const
enterpriseAuthContactBusinessUrl
=
export
const
enterpriseAuthContactBusinessUrl
=
'https://fael3z0zfze.feishu.cn/share/base/form/shrcnjJWtKqjOI9NbQTzhNyzljc?prefill_S=C2&hide_S=1&from=navigation'
;
'https://fael3z0zfze.feishu.cn/share/base/form/shrcnjJWtKqjOI9NbQTzhNyzljc?prefill_S=C2&hide_S=1&from=navigation'
;
...
@@ -31,6 +28,20 @@ export const canOpenEnterpriseAuthAmountStep = (
...
@@ -31,6 +28,20 @@ export const canOpenEnterpriseAuthAmountStep = (
taskStatus
===
TeamEnterpriseAuthTaskStatusEnum
.
amount_failed
;
taskStatus
===
TeamEnterpriseAuthTaskStatusEnum
.
amount_failed
;
/**
/**
* 判断企业认证入口是否应该转为商务咨询弹窗。
*
* 次数耗尽但仍有金额验证任务时,用户需要继续完成当前任务;只有没有可继续任务时,
* 才在入口按钮处直接提示联系商务。
*/
export
const
shouldShowEnterpriseAuthContactBusinessModal
=
({
hasRemainingAuthTimes
,
hasCurrentTask
}:
{
hasRemainingAuthTimes
?:
boolean
;
hasCurrentTask
:
boolean
;
})
=>
hasRemainingAuthTimes
===
false
&&
!
hasCurrentTask
;
/**
* 判断当前成员是否可以发起或继续企业认证。
* 判断当前成员是否可以发起或继续企业认证。
*
*
* 团队 owner 和团队管理员才有企业认证操作入口;statusCanManage 来自服务端状态接口,
* 团队 owner 和团队管理员才有企业认证操作入口;statusCanManage 来自服务端状态接口,
...
@@ -45,17 +56,3 @@ export const canManageEnterpriseAuth = ({
...
@@ -45,17 +56,3 @@ export const canManageEnterpriseAuth = ({
isTeamOwner
?:
boolean
;
isTeamOwner
?:
boolean
;
hasTeamManagePer
?:
boolean
;
hasTeamManagePer
?:
boolean
;
})
=>
(
!!
isTeamOwner
||
!!
hasTeamManagePer
)
&&
statusCanManage
!==
false
;
})
=>
(
!!
isTeamOwner
||
!!
hasTeamManagePer
)
&&
statusCanManage
!==
false
;
/**
* 判断企业认证入口是否应该转为商务咨询弹窗。
*
* 第 3 次认证发起成功后 usedTimes 会达到上限,但此时会返回 currentTask,
* 用户仍需要继续填写打款金额;只有次数耗尽且没有可恢复任务时,才阻断认证表单。
*/
export
const
shouldShowEnterpriseAuthContactBusinessModal
=
({
usedTimes
,
hasCurrentTask
}:
{
usedTimes
?:
number
;
hasCurrentTask
:
boolean
;
})
=>
usedTimes
!==
undefined
&&
usedTimes
>=
EnterpriseAuthMaxTimes
&&
!
hasCurrentTask
;
projects/app/test/pageComponents/account/team/EnterpriseAuth/utils.test.ts
View file @
e11aa7a8
import
{
describe
,
expect
,
it
}
from
'vitest'
;
import
{
describe
,
expect
,
it
}
from
'vitest'
;
import
{
import
{
canManageEnterpriseAuth
,
canManageEnterpriseAuth
,
shouldShowEnterpriseAuth
AmountError
,
shouldShowEnterpriseAuth
ContactBusinessModal
,
shouldShowEnterpriseAuth
ContactBusinessModal
shouldShowEnterpriseAuth
AmountError
}
from
'../../../../../src/pageComponents/account/team/EnterpriseAuth/utils'
;
}
from
'../../../../../src/pageComponents/account/team/EnterpriseAuth/utils'
;
import
{
import
{
TeamEnterpriseAuthTaskStatusEnum
}
from
'@fastgpt/global/support/user/team/enterpriseAuth/constant'
;
EnterpriseAuthMaxTimes
,
TeamEnterpriseAuthTaskStatusEnum
}
from
'@fastgpt/global/support/user/team/enterpriseAuth/constant'
;
describe
(
'shouldShowEnterpriseAuthAmountError'
,
()
=>
{
describe
(
'shouldShowEnterpriseAuthAmountError'
,
()
=>
{
it
(
'历史 amount_failed 任务重新进入时不展示金额错误'
,
()
=>
{
it
(
'历史 amount_failed 任务重新进入时不展示金额错误'
,
()
=>
{
...
@@ -78,30 +75,21 @@ describe('canManageEnterpriseAuth', () => {
...
@@ -78,30 +75,21 @@ describe('canManageEnterpriseAuth', () => {
});
});
describe
(
'shouldShowEnterpriseAuthContactBusinessModal'
,
()
=>
{
describe
(
'shouldShowEnterpriseAuthContactBusinessModal'
,
()
=>
{
it
(
'认证次数耗尽且没有当前任务时
展示商务咨询弹窗
'
,
()
=>
{
it
(
'认证次数耗尽且没有当前任务时
提示联系商务
'
,
()
=>
{
expect
(
expect
(
shouldShowEnterpriseAuthContactBusinessModal
({
shouldShowEnterpriseAuthContactBusinessModal
({
usedTimes
:
EnterpriseAuthMaxTimes
,
hasRemainingAuthTimes
:
false
,
hasCurrentTask
:
false
hasCurrentTask
:
false
})
})
).
toBe
(
true
);
).
toBe
(
true
);
});
});
it
(
'认证次数耗尽但
存在待确认打款任务时继续允许恢复认证
'
,
()
=>
{
it
(
'认证次数耗尽但
仍有当前金额验证任务时允许继续任务
'
,
()
=>
{
expect
(
expect
(
shouldShowEnterpriseAuthContactBusinessModal
({
shouldShowEnterpriseAuthContactBusinessModal
({
usedTimes
:
EnterpriseAuthMaxTimes
,
hasRemainingAuthTimes
:
false
,
hasCurrentTask
:
true
hasCurrentTask
:
true
})
})
).
toBe
(
false
);
).
toBe
(
false
);
});
});
it
(
'认证状态未加载完成时不提前展示商务咨询弹窗'
,
()
=>
{
expect
(
shouldShowEnterpriseAuthContactBusinessModal
({
usedTimes
:
undefined
,
hasCurrentTask
:
false
})
).
toBe
(
false
);
});
});
});
test/mocks/common/redis.ts
View file @
e11aa7a8
...
@@ -70,6 +70,25 @@ const createRedisStorage = () => {
...
@@ -70,6 +70,25 @@ const createRedisStorage = () => {
});
});
return
count
;
return
count
;
},
},
expire
:
(
key
:
string
,
seconds
:
number
,
mode
?:
string
)
=>
{
if
(
isExpired
(
key
)
||
!
storage
.
has
(
key
))
return
0
;
if
(
String
(
mode
??
''
).
toUpperCase
()
===
'NX'
&&
expiryMap
.
has
(
key
))
return
0
;
expiryMap
.
set
(
key
,
Date
.
now
()
+
seconds
*
1000
);
return
1
;
},
ttl
:
(
key
:
string
)
=>
{
if
(
isExpired
(
key
)
||
!
storage
.
has
(
key
))
return
-
2
;
const
expiry
=
expiryMap
.
get
(
key
);
if
(
!
expiry
)
return
-
1
;
return
Math
.
max
(
0
,
Math
.
ceil
((
expiry
-
Date
.
now
())
/
1000
));
},
incr
:
(
key
:
string
)
=>
{
if
(
isExpired
(
key
))
storage
.
delete
(
key
);
const
current
=
Number
(
storage
.
get
(
key
)
??
0
);
const
next
=
current
+
1
;
storage
.
set
(
key
,
next
);
return
next
;
},
pexpire
:
(
key
:
string
,
milliseconds
:
number
)
=>
{
pexpire
:
(
key
:
string
,
milliseconds
:
number
)
=>
{
if
(
isExpired
(
key
)
||
!
storage
.
has
(
key
))
return
0
;
if
(
isExpired
(
key
)
||
!
storage
.
has
(
key
))
return
0
;
expiryMap
.
set
(
key
,
Date
.
now
()
+
milliseconds
);
expiryMap
.
set
(
key
,
Date
.
now
()
+
milliseconds
);
...
@@ -153,12 +172,18 @@ const createSharedMockRedisClient = () => {
...
@@ -153,12 +172,18 @@ const createSharedMockRedisClient = () => {
hmset
:
vi
.
fn
().
mockResolvedValue
(
'OK'
),
hmset
:
vi
.
fn
().
mockResolvedValue
(
'OK'
),
// Expiry operations
// Expiry operations
expire
:
vi
.
fn
().
mockResolvedValue
(
1
),
expire
:
vi
ttl
:
vi
.
fn
().
mockResolvedValue
(
-
1
),
.
fn
()
.
mockImplementation
((
key
:
string
,
seconds
:
number
,
mode
?:
string
)
=>
Promise
.
resolve
(
globalRedisStorage
.
expire
(
key
,
seconds
,
mode
))
),
ttl
:
vi
.
fn
().
mockImplementation
((
key
:
string
)
=>
Promise
.
resolve
(
globalRedisStorage
.
ttl
(
key
))),
expireat
:
vi
.
fn
().
mockResolvedValue
(
1
),
expireat
:
vi
.
fn
().
mockResolvedValue
(
1
),
// Increment operations
// Increment operations
incr
:
vi
.
fn
().
mockResolvedValue
(
1
),
incr
:
vi
.
fn
()
.
mockImplementation
((
key
:
string
)
=>
Promise
.
resolve
(
globalRedisStorage
.
incr
(
key
))),
decr
:
vi
.
fn
().
mockResolvedValue
(
1
),
decr
:
vi
.
fn
().
mockResolvedValue
(
1
),
incrby
:
vi
.
fn
().
mockResolvedValue
(
1
),
incrby
:
vi
.
fn
().
mockResolvedValue
(
1
),
decrby
:
vi
.
fn
().
mockResolvedValue
(
1
),
decrby
:
vi
.
fn
().
mockResolvedValue
(
1
),
...
@@ -196,11 +221,23 @@ const createSharedMockRedisClient = () => {
...
@@ -196,11 +221,23 @@ const createSharedMockRedisClient = () => {
unlink
:
vi
.
fn
().
mockReturnThis
(),
unlink
:
vi
.
fn
().
mockReturnThis
(),
exec
:
vi
.
fn
().
mockResolvedValue
([])
exec
:
vi
.
fn
().
mockResolvedValue
([])
})),
})),
multi
:
vi
.
fn
(()
=>
({
multi
:
vi
.
fn
(()
=>
{
incr
:
vi
.
fn
().
mockReturnThis
(),
const
commands
:
Array
<
()
=>
[
null
,
unknown
]
>
=
[];
expire
:
vi
.
fn
().
mockReturnThis
(),
const
pipeline
=
{
exec
:
vi
.
fn
().
mockResolvedValue
([[
null
,
1
]])
incr
:
vi
.
fn
((
key
:
string
)
=>
{
})),
commands
.
push
(()
=>
[
null
,
globalRedisStorage
.
incr
(
key
)]);
return
pipeline
;
}),
expire
:
vi
.
fn
((
key
:
string
,
seconds
:
number
,
mode
?:
string
)
=>
{
commands
.
push
(()
=>
[
null
,
globalRedisStorage
.
expire
(
key
,
seconds
,
mode
)]);
return
pipeline
;
}),
exec
:
vi
.
fn
()
.
mockImplementation
(()
=>
Promise
.
resolve
(
commands
.
map
((
command
)
=>
command
())))
};
return
pipeline
;
}),
// Internal storage for testing purposes
// Internal storage for testing purposes
_storage
:
globalRedisStorage
_storage
:
globalRedisStorage
...
...
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