Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
8da5fac6
"...src/components/git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "3dfb62e99627c2453e509c2f2ff55866d8329edd"
Unverified
Commit
8da5fac6
authored
Feb 11, 2026
by
程序猿MT
Committed by
GitHub
Feb 11, 2026
Browse files
Merge branch 'Wei-Shaw:main' into main
parents
1dd3158c
72310276
Changes
37
Expand all
Show whitespace changes
Inline
Side-by-side
backend/ent/errorpassthroughrule.go
View file @
8da5fac6
...
@@ -44,6 +44,8 @@ type ErrorPassthroughRule struct {
...
@@ -44,6 +44,8 @@ type ErrorPassthroughRule struct {
PassthroughBody
bool
`json:"passthrough_body,omitempty"`
PassthroughBody
bool
`json:"passthrough_body,omitempty"`
// CustomMessage holds the value of the "custom_message" field.
// CustomMessage holds the value of the "custom_message" field.
CustomMessage
*
string
`json:"custom_message,omitempty"`
CustomMessage
*
string
`json:"custom_message,omitempty"`
// SkipMonitoring holds the value of the "skip_monitoring" field.
SkipMonitoring
bool
`json:"skip_monitoring,omitempty"`
// Description holds the value of the "description" field.
// Description holds the value of the "description" field.
Description
*
string
`json:"description,omitempty"`
Description
*
string
`json:"description,omitempty"`
selectValues
sql
.
SelectValues
selectValues
sql
.
SelectValues
...
@@ -56,7 +58,7 @@ func (*ErrorPassthroughRule) scanValues(columns []string) ([]any, error) {
...
@@ -56,7 +58,7 @@ func (*ErrorPassthroughRule) scanValues(columns []string) ([]any, error) {
switch
columns
[
i
]
{
switch
columns
[
i
]
{
case
errorpassthroughrule
.
FieldErrorCodes
,
errorpassthroughrule
.
FieldKeywords
,
errorpassthroughrule
.
FieldPlatforms
:
case
errorpassthroughrule
.
FieldErrorCodes
,
errorpassthroughrule
.
FieldKeywords
,
errorpassthroughrule
.
FieldPlatforms
:
values
[
i
]
=
new
([]
byte
)
values
[
i
]
=
new
([]
byte
)
case
errorpassthroughrule
.
FieldEnabled
,
errorpassthroughrule
.
FieldPassthroughCode
,
errorpassthroughrule
.
FieldPassthroughBody
:
case
errorpassthroughrule
.
FieldEnabled
,
errorpassthroughrule
.
FieldPassthroughCode
,
errorpassthroughrule
.
FieldPassthroughBody
,
errorpassthroughrule
.
FieldSkipMonitoring
:
values
[
i
]
=
new
(
sql
.
NullBool
)
values
[
i
]
=
new
(
sql
.
NullBool
)
case
errorpassthroughrule
.
FieldID
,
errorpassthroughrule
.
FieldPriority
,
errorpassthroughrule
.
FieldResponseCode
:
case
errorpassthroughrule
.
FieldID
,
errorpassthroughrule
.
FieldPriority
,
errorpassthroughrule
.
FieldResponseCode
:
values
[
i
]
=
new
(
sql
.
NullInt64
)
values
[
i
]
=
new
(
sql
.
NullInt64
)
...
@@ -171,6 +173,12 @@ func (_m *ErrorPassthroughRule) assignValues(columns []string, values []any) err
...
@@ -171,6 +173,12 @@ func (_m *ErrorPassthroughRule) assignValues(columns []string, values []any) err
_m
.
CustomMessage
=
new
(
string
)
_m
.
CustomMessage
=
new
(
string
)
*
_m
.
CustomMessage
=
value
.
String
*
_m
.
CustomMessage
=
value
.
String
}
}
case
errorpassthroughrule
.
FieldSkipMonitoring
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullBool
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field skip_monitoring"
,
values
[
i
])
}
else
if
value
.
Valid
{
_m
.
SkipMonitoring
=
value
.
Bool
}
case
errorpassthroughrule
.
FieldDescription
:
case
errorpassthroughrule
.
FieldDescription
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullString
);
!
ok
{
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullString
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field description"
,
values
[
i
])
return
fmt
.
Errorf
(
"unexpected type %T for field description"
,
values
[
i
])
...
@@ -257,6 +265,9 @@ func (_m *ErrorPassthroughRule) String() string {
...
@@ -257,6 +265,9 @@ func (_m *ErrorPassthroughRule) String() string {
builder
.
WriteString
(
*
v
)
builder
.
WriteString
(
*
v
)
}
}
builder
.
WriteString
(
", "
)
builder
.
WriteString
(
", "
)
builder
.
WriteString
(
"skip_monitoring="
)
builder
.
WriteString
(
fmt
.
Sprintf
(
"%v"
,
_m
.
SkipMonitoring
))
builder
.
WriteString
(
", "
)
if
v
:=
_m
.
Description
;
v
!=
nil
{
if
v
:=
_m
.
Description
;
v
!=
nil
{
builder
.
WriteString
(
"description="
)
builder
.
WriteString
(
"description="
)
builder
.
WriteString
(
*
v
)
builder
.
WriteString
(
*
v
)
...
...
backend/ent/errorpassthroughrule/errorpassthroughrule.go
View file @
8da5fac6
...
@@ -39,6 +39,8 @@ const (
...
@@ -39,6 +39,8 @@ const (
FieldPassthroughBody
=
"passthrough_body"
FieldPassthroughBody
=
"passthrough_body"
// FieldCustomMessage holds the string denoting the custom_message field in the database.
// FieldCustomMessage holds the string denoting the custom_message field in the database.
FieldCustomMessage
=
"custom_message"
FieldCustomMessage
=
"custom_message"
// FieldSkipMonitoring holds the string denoting the skip_monitoring field in the database.
FieldSkipMonitoring
=
"skip_monitoring"
// FieldDescription holds the string denoting the description field in the database.
// FieldDescription holds the string denoting the description field in the database.
FieldDescription
=
"description"
FieldDescription
=
"description"
// Table holds the table name of the errorpassthroughrule in the database.
// Table holds the table name of the errorpassthroughrule in the database.
...
@@ -61,6 +63,7 @@ var Columns = []string{
...
@@ -61,6 +63,7 @@ var Columns = []string{
FieldResponseCode
,
FieldResponseCode
,
FieldPassthroughBody
,
FieldPassthroughBody
,
FieldCustomMessage
,
FieldCustomMessage
,
FieldSkipMonitoring
,
FieldDescription
,
FieldDescription
,
}
}
...
@@ -95,6 +98,8 @@ var (
...
@@ -95,6 +98,8 @@ var (
DefaultPassthroughCode
bool
DefaultPassthroughCode
bool
// DefaultPassthroughBody holds the default value on creation for the "passthrough_body" field.
// DefaultPassthroughBody holds the default value on creation for the "passthrough_body" field.
DefaultPassthroughBody
bool
DefaultPassthroughBody
bool
// DefaultSkipMonitoring holds the default value on creation for the "skip_monitoring" field.
DefaultSkipMonitoring
bool
)
)
// OrderOption defines the ordering options for the ErrorPassthroughRule queries.
// OrderOption defines the ordering options for the ErrorPassthroughRule queries.
...
@@ -155,6 +160,11 @@ func ByCustomMessage(opts ...sql.OrderTermOption) OrderOption {
...
@@ -155,6 +160,11 @@ func ByCustomMessage(opts ...sql.OrderTermOption) OrderOption {
return
sql
.
OrderByField
(
FieldCustomMessage
,
opts
...
)
.
ToFunc
()
return
sql
.
OrderByField
(
FieldCustomMessage
,
opts
...
)
.
ToFunc
()
}
}
// BySkipMonitoring orders the results by the skip_monitoring field.
func
BySkipMonitoring
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldSkipMonitoring
,
opts
...
)
.
ToFunc
()
}
// ByDescription orders the results by the description field.
// ByDescription orders the results by the description field.
func
ByDescription
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
func
ByDescription
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldDescription
,
opts
...
)
.
ToFunc
()
return
sql
.
OrderByField
(
FieldDescription
,
opts
...
)
.
ToFunc
()
...
...
backend/ent/errorpassthroughrule/where.go
View file @
8da5fac6
...
@@ -104,6 +104,11 @@ func CustomMessage(v string) predicate.ErrorPassthroughRule {
...
@@ -104,6 +104,11 @@ func CustomMessage(v string) predicate.ErrorPassthroughRule {
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldCustomMessage
,
v
))
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldCustomMessage
,
v
))
}
}
// SkipMonitoring applies equality check predicate on the "skip_monitoring" field. It's identical to SkipMonitoringEQ.
func
SkipMonitoring
(
v
bool
)
predicate
.
ErrorPassthroughRule
{
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldSkipMonitoring
,
v
))
}
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
func
Description
(
v
string
)
predicate
.
ErrorPassthroughRule
{
func
Description
(
v
string
)
predicate
.
ErrorPassthroughRule
{
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldDescription
,
v
))
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldDescription
,
v
))
...
@@ -544,6 +549,16 @@ func CustomMessageContainsFold(v string) predicate.ErrorPassthroughRule {
...
@@ -544,6 +549,16 @@ func CustomMessageContainsFold(v string) predicate.ErrorPassthroughRule {
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldContainsFold
(
FieldCustomMessage
,
v
))
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldContainsFold
(
FieldCustomMessage
,
v
))
}
}
// SkipMonitoringEQ applies the EQ predicate on the "skip_monitoring" field.
func
SkipMonitoringEQ
(
v
bool
)
predicate
.
ErrorPassthroughRule
{
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldSkipMonitoring
,
v
))
}
// SkipMonitoringNEQ applies the NEQ predicate on the "skip_monitoring" field.
func
SkipMonitoringNEQ
(
v
bool
)
predicate
.
ErrorPassthroughRule
{
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldNEQ
(
FieldSkipMonitoring
,
v
))
}
// DescriptionEQ applies the EQ predicate on the "description" field.
// DescriptionEQ applies the EQ predicate on the "description" field.
func
DescriptionEQ
(
v
string
)
predicate
.
ErrorPassthroughRule
{
func
DescriptionEQ
(
v
string
)
predicate
.
ErrorPassthroughRule
{
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldDescription
,
v
))
return
predicate
.
ErrorPassthroughRule
(
sql
.
FieldEQ
(
FieldDescription
,
v
))
...
...
backend/ent/errorpassthroughrule_create.go
View file @
8da5fac6
...
@@ -172,6 +172,20 @@ func (_c *ErrorPassthroughRuleCreate) SetNillableCustomMessage(v *string) *Error
...
@@ -172,6 +172,20 @@ func (_c *ErrorPassthroughRuleCreate) SetNillableCustomMessage(v *string) *Error
return
_c
return
_c
}
}
// SetSkipMonitoring sets the "skip_monitoring" field.
func
(
_c
*
ErrorPassthroughRuleCreate
)
SetSkipMonitoring
(
v
bool
)
*
ErrorPassthroughRuleCreate
{
_c
.
mutation
.
SetSkipMonitoring
(
v
)
return
_c
}
// SetNillableSkipMonitoring sets the "skip_monitoring" field if the given value is not nil.
func
(
_c
*
ErrorPassthroughRuleCreate
)
SetNillableSkipMonitoring
(
v
*
bool
)
*
ErrorPassthroughRuleCreate
{
if
v
!=
nil
{
_c
.
SetSkipMonitoring
(
*
v
)
}
return
_c
}
// SetDescription sets the "description" field.
// SetDescription sets the "description" field.
func
(
_c
*
ErrorPassthroughRuleCreate
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleCreate
{
func
(
_c
*
ErrorPassthroughRuleCreate
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleCreate
{
_c
.
mutation
.
SetDescription
(
v
)
_c
.
mutation
.
SetDescription
(
v
)
...
@@ -249,6 +263,10 @@ func (_c *ErrorPassthroughRuleCreate) defaults() {
...
@@ -249,6 +263,10 @@ func (_c *ErrorPassthroughRuleCreate) defaults() {
v
:=
errorpassthroughrule
.
DefaultPassthroughBody
v
:=
errorpassthroughrule
.
DefaultPassthroughBody
_c
.
mutation
.
SetPassthroughBody
(
v
)
_c
.
mutation
.
SetPassthroughBody
(
v
)
}
}
if
_
,
ok
:=
_c
.
mutation
.
SkipMonitoring
();
!
ok
{
v
:=
errorpassthroughrule
.
DefaultSkipMonitoring
_c
.
mutation
.
SetSkipMonitoring
(
v
)
}
}
}
// check runs all checks and user-defined validators on the builder.
// check runs all checks and user-defined validators on the builder.
...
@@ -287,6 +305,9 @@ func (_c *ErrorPassthroughRuleCreate) check() error {
...
@@ -287,6 +305,9 @@ func (_c *ErrorPassthroughRuleCreate) check() error {
if
_
,
ok
:=
_c
.
mutation
.
PassthroughBody
();
!
ok
{
if
_
,
ok
:=
_c
.
mutation
.
PassthroughBody
();
!
ok
{
return
&
ValidationError
{
Name
:
"passthrough_body"
,
err
:
errors
.
New
(
`ent: missing required field "ErrorPassthroughRule.passthrough_body"`
)}
return
&
ValidationError
{
Name
:
"passthrough_body"
,
err
:
errors
.
New
(
`ent: missing required field "ErrorPassthroughRule.passthrough_body"`
)}
}
}
if
_
,
ok
:=
_c
.
mutation
.
SkipMonitoring
();
!
ok
{
return
&
ValidationError
{
Name
:
"skip_monitoring"
,
err
:
errors
.
New
(
`ent: missing required field "ErrorPassthroughRule.skip_monitoring"`
)}
}
return
nil
return
nil
}
}
...
@@ -366,6 +387,10 @@ func (_c *ErrorPassthroughRuleCreate) createSpec() (*ErrorPassthroughRule, *sqlg
...
@@ -366,6 +387,10 @@ func (_c *ErrorPassthroughRuleCreate) createSpec() (*ErrorPassthroughRule, *sqlg
_spec
.
SetField
(
errorpassthroughrule
.
FieldCustomMessage
,
field
.
TypeString
,
value
)
_spec
.
SetField
(
errorpassthroughrule
.
FieldCustomMessage
,
field
.
TypeString
,
value
)
_node
.
CustomMessage
=
&
value
_node
.
CustomMessage
=
&
value
}
}
if
value
,
ok
:=
_c
.
mutation
.
SkipMonitoring
();
ok
{
_spec
.
SetField
(
errorpassthroughrule
.
FieldSkipMonitoring
,
field
.
TypeBool
,
value
)
_node
.
SkipMonitoring
=
value
}
if
value
,
ok
:=
_c
.
mutation
.
Description
();
ok
{
if
value
,
ok
:=
_c
.
mutation
.
Description
();
ok
{
_spec
.
SetField
(
errorpassthroughrule
.
FieldDescription
,
field
.
TypeString
,
value
)
_spec
.
SetField
(
errorpassthroughrule
.
FieldDescription
,
field
.
TypeString
,
value
)
_node
.
Description
=
&
value
_node
.
Description
=
&
value
...
@@ -608,6 +633,18 @@ func (u *ErrorPassthroughRuleUpsert) ClearCustomMessage() *ErrorPassthroughRuleU
...
@@ -608,6 +633,18 @@ func (u *ErrorPassthroughRuleUpsert) ClearCustomMessage() *ErrorPassthroughRuleU
return
u
return
u
}
}
// SetSkipMonitoring sets the "skip_monitoring" field.
func
(
u
*
ErrorPassthroughRuleUpsert
)
SetSkipMonitoring
(
v
bool
)
*
ErrorPassthroughRuleUpsert
{
u
.
Set
(
errorpassthroughrule
.
FieldSkipMonitoring
,
v
)
return
u
}
// UpdateSkipMonitoring sets the "skip_monitoring" field to the value that was provided on create.
func
(
u
*
ErrorPassthroughRuleUpsert
)
UpdateSkipMonitoring
()
*
ErrorPassthroughRuleUpsert
{
u
.
SetExcluded
(
errorpassthroughrule
.
FieldSkipMonitoring
)
return
u
}
// SetDescription sets the "description" field.
// SetDescription sets the "description" field.
func
(
u
*
ErrorPassthroughRuleUpsert
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpsert
{
func
(
u
*
ErrorPassthroughRuleUpsert
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpsert
{
u
.
Set
(
errorpassthroughrule
.
FieldDescription
,
v
)
u
.
Set
(
errorpassthroughrule
.
FieldDescription
,
v
)
...
@@ -888,6 +925,20 @@ func (u *ErrorPassthroughRuleUpsertOne) ClearCustomMessage() *ErrorPassthroughRu
...
@@ -888,6 +925,20 @@ func (u *ErrorPassthroughRuleUpsertOne) ClearCustomMessage() *ErrorPassthroughRu
})
})
}
}
// SetSkipMonitoring sets the "skip_monitoring" field.
func
(
u
*
ErrorPassthroughRuleUpsertOne
)
SetSkipMonitoring
(
v
bool
)
*
ErrorPassthroughRuleUpsertOne
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
s
.
SetSkipMonitoring
(
v
)
})
}
// UpdateSkipMonitoring sets the "skip_monitoring" field to the value that was provided on create.
func
(
u
*
ErrorPassthroughRuleUpsertOne
)
UpdateSkipMonitoring
()
*
ErrorPassthroughRuleUpsertOne
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
s
.
UpdateSkipMonitoring
()
})
}
// SetDescription sets the "description" field.
// SetDescription sets the "description" field.
func
(
u
*
ErrorPassthroughRuleUpsertOne
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpsertOne
{
func
(
u
*
ErrorPassthroughRuleUpsertOne
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpsertOne
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
...
@@ -1337,6 +1388,20 @@ func (u *ErrorPassthroughRuleUpsertBulk) ClearCustomMessage() *ErrorPassthroughR
...
@@ -1337,6 +1388,20 @@ func (u *ErrorPassthroughRuleUpsertBulk) ClearCustomMessage() *ErrorPassthroughR
})
})
}
}
// SetSkipMonitoring sets the "skip_monitoring" field.
func
(
u
*
ErrorPassthroughRuleUpsertBulk
)
SetSkipMonitoring
(
v
bool
)
*
ErrorPassthroughRuleUpsertBulk
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
s
.
SetSkipMonitoring
(
v
)
})
}
// UpdateSkipMonitoring sets the "skip_monitoring" field to the value that was provided on create.
func
(
u
*
ErrorPassthroughRuleUpsertBulk
)
UpdateSkipMonitoring
()
*
ErrorPassthroughRuleUpsertBulk
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
s
.
UpdateSkipMonitoring
()
})
}
// SetDescription sets the "description" field.
// SetDescription sets the "description" field.
func
(
u
*
ErrorPassthroughRuleUpsertBulk
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpsertBulk
{
func
(
u
*
ErrorPassthroughRuleUpsertBulk
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpsertBulk
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
return
u
.
Update
(
func
(
s
*
ErrorPassthroughRuleUpsert
)
{
...
...
backend/ent/errorpassthroughrule_update.go
View file @
8da5fac6
...
@@ -227,6 +227,20 @@ func (_u *ErrorPassthroughRuleUpdate) ClearCustomMessage() *ErrorPassthroughRule
...
@@ -227,6 +227,20 @@ func (_u *ErrorPassthroughRuleUpdate) ClearCustomMessage() *ErrorPassthroughRule
return
_u
return
_u
}
}
// SetSkipMonitoring sets the "skip_monitoring" field.
func
(
_u
*
ErrorPassthroughRuleUpdate
)
SetSkipMonitoring
(
v
bool
)
*
ErrorPassthroughRuleUpdate
{
_u
.
mutation
.
SetSkipMonitoring
(
v
)
return
_u
}
// SetNillableSkipMonitoring sets the "skip_monitoring" field if the given value is not nil.
func
(
_u
*
ErrorPassthroughRuleUpdate
)
SetNillableSkipMonitoring
(
v
*
bool
)
*
ErrorPassthroughRuleUpdate
{
if
v
!=
nil
{
_u
.
SetSkipMonitoring
(
*
v
)
}
return
_u
}
// SetDescription sets the "description" field.
// SetDescription sets the "description" field.
func
(
_u
*
ErrorPassthroughRuleUpdate
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpdate
{
func
(
_u
*
ErrorPassthroughRuleUpdate
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpdate
{
_u
.
mutation
.
SetDescription
(
v
)
_u
.
mutation
.
SetDescription
(
v
)
...
@@ -387,6 +401,9 @@ func (_u *ErrorPassthroughRuleUpdate) sqlSave(ctx context.Context) (_node int, e
...
@@ -387,6 +401,9 @@ func (_u *ErrorPassthroughRuleUpdate) sqlSave(ctx context.Context) (_node int, e
if
_u
.
mutation
.
CustomMessageCleared
()
{
if
_u
.
mutation
.
CustomMessageCleared
()
{
_spec
.
ClearField
(
errorpassthroughrule
.
FieldCustomMessage
,
field
.
TypeString
)
_spec
.
ClearField
(
errorpassthroughrule
.
FieldCustomMessage
,
field
.
TypeString
)
}
}
if
value
,
ok
:=
_u
.
mutation
.
SkipMonitoring
();
ok
{
_spec
.
SetField
(
errorpassthroughrule
.
FieldSkipMonitoring
,
field
.
TypeBool
,
value
)
}
if
value
,
ok
:=
_u
.
mutation
.
Description
();
ok
{
if
value
,
ok
:=
_u
.
mutation
.
Description
();
ok
{
_spec
.
SetField
(
errorpassthroughrule
.
FieldDescription
,
field
.
TypeString
,
value
)
_spec
.
SetField
(
errorpassthroughrule
.
FieldDescription
,
field
.
TypeString
,
value
)
}
}
...
@@ -611,6 +628,20 @@ func (_u *ErrorPassthroughRuleUpdateOne) ClearCustomMessage() *ErrorPassthroughR
...
@@ -611,6 +628,20 @@ func (_u *ErrorPassthroughRuleUpdateOne) ClearCustomMessage() *ErrorPassthroughR
return
_u
return
_u
}
}
// SetSkipMonitoring sets the "skip_monitoring" field.
func
(
_u
*
ErrorPassthroughRuleUpdateOne
)
SetSkipMonitoring
(
v
bool
)
*
ErrorPassthroughRuleUpdateOne
{
_u
.
mutation
.
SetSkipMonitoring
(
v
)
return
_u
}
// SetNillableSkipMonitoring sets the "skip_monitoring" field if the given value is not nil.
func
(
_u
*
ErrorPassthroughRuleUpdateOne
)
SetNillableSkipMonitoring
(
v
*
bool
)
*
ErrorPassthroughRuleUpdateOne
{
if
v
!=
nil
{
_u
.
SetSkipMonitoring
(
*
v
)
}
return
_u
}
// SetDescription sets the "description" field.
// SetDescription sets the "description" field.
func
(
_u
*
ErrorPassthroughRuleUpdateOne
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpdateOne
{
func
(
_u
*
ErrorPassthroughRuleUpdateOne
)
SetDescription
(
v
string
)
*
ErrorPassthroughRuleUpdateOne
{
_u
.
mutation
.
SetDescription
(
v
)
_u
.
mutation
.
SetDescription
(
v
)
...
@@ -801,6 +832,9 @@ func (_u *ErrorPassthroughRuleUpdateOne) sqlSave(ctx context.Context) (_node *Er
...
@@ -801,6 +832,9 @@ func (_u *ErrorPassthroughRuleUpdateOne) sqlSave(ctx context.Context) (_node *Er
if
_u
.
mutation
.
CustomMessageCleared
()
{
if
_u
.
mutation
.
CustomMessageCleared
()
{
_spec
.
ClearField
(
errorpassthroughrule
.
FieldCustomMessage
,
field
.
TypeString
)
_spec
.
ClearField
(
errorpassthroughrule
.
FieldCustomMessage
,
field
.
TypeString
)
}
}
if
value
,
ok
:=
_u
.
mutation
.
SkipMonitoring
();
ok
{
_spec
.
SetField
(
errorpassthroughrule
.
FieldSkipMonitoring
,
field
.
TypeBool
,
value
)
}
if
value
,
ok
:=
_u
.
mutation
.
Description
();
ok
{
if
value
,
ok
:=
_u
.
mutation
.
Description
();
ok
{
_spec
.
SetField
(
errorpassthroughrule
.
FieldDescription
,
field
.
TypeString
,
value
)
_spec
.
SetField
(
errorpassthroughrule
.
FieldDescription
,
field
.
TypeString
,
value
)
}
}
...
...
backend/ent/migrate/schema.go
View file @
8da5fac6
...
@@ -325,6 +325,7 @@ var (
...
@@ -325,6 +325,7 @@ var (
{
Name
:
"response_code"
,
Type
:
field
.
TypeInt
,
Nullable
:
true
},
{
Name
:
"response_code"
,
Type
:
field
.
TypeInt
,
Nullable
:
true
},
{
Name
:
"passthrough_body"
,
Type
:
field
.
TypeBool
,
Default
:
true
},
{
Name
:
"passthrough_body"
,
Type
:
field
.
TypeBool
,
Default
:
true
},
{
Name
:
"custom_message"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
2147483647
},
{
Name
:
"custom_message"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
2147483647
},
{
Name
:
"skip_monitoring"
,
Type
:
field
.
TypeBool
,
Default
:
false
},
{
Name
:
"description"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
2147483647
},
{
Name
:
"description"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
2147483647
},
}
}
// ErrorPassthroughRulesTable holds the schema information for the "error_passthrough_rules" table.
// ErrorPassthroughRulesTable holds the schema information for the "error_passthrough_rules" table.
...
...
backend/ent/mutation.go
View file @
8da5fac6
...
@@ -5776,6 +5776,7 @@ type ErrorPassthroughRuleMutation struct {
...
@@ -5776,6 +5776,7 @@ type ErrorPassthroughRuleMutation struct {
addresponse_code *int
addresponse_code *int
passthrough_body *bool
passthrough_body *bool
custom_message *string
custom_message *string
skip_monitoring *bool
description *string
description *string
clearedFields map[string]struct{}
clearedFields map[string]struct{}
done bool
done bool
...
@@ -6503,6 +6504,42 @@ func (m *ErrorPassthroughRuleMutation) ResetCustomMessage() {
...
@@ -6503,6 +6504,42 @@ func (m *ErrorPassthroughRuleMutation) ResetCustomMessage() {
delete(m.clearedFields, errorpassthroughrule.FieldCustomMessage)
delete(m.clearedFields, errorpassthroughrule.FieldCustomMessage)
}
}
// SetSkipMonitoring sets the "skip_monitoring" field.
func (m *ErrorPassthroughRuleMutation) SetSkipMonitoring(b bool) {
m.skip_monitoring = &b
}
// SkipMonitoring returns the value of the "skip_monitoring" field in the mutation.
func (m *ErrorPassthroughRuleMutation) SkipMonitoring() (r bool, exists bool) {
v := m.skip_monitoring
if v == nil {
return
}
return *v, true
}
// OldSkipMonitoring returns the old "skip_monitoring" field's value of the ErrorPassthroughRule entity.
// If the ErrorPassthroughRule object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ErrorPassthroughRuleMutation) OldSkipMonitoring(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSkipMonitoring is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSkipMonitoring requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSkipMonitoring: %w", err)
}
return oldValue.SkipMonitoring, nil
}
// ResetSkipMonitoring resets all changes to the "skip_monitoring" field.
func (m *ErrorPassthroughRuleMutation) ResetSkipMonitoring() {
m.skip_monitoring = nil
}
// SetDescription sets the "description" field.
// SetDescription sets the "description" field.
func (m *ErrorPassthroughRuleMutation) SetDescription(s string) {
func (m *ErrorPassthroughRuleMutation) SetDescription(s string) {
m.description = &s
m.description = &s
...
@@ -6586,7 +6623,7 @@ func (m *ErrorPassthroughRuleMutation) Type() string {
...
@@ -6586,7 +6623,7 @@ func (m *ErrorPassthroughRuleMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
// AddedFields().
func (m *ErrorPassthroughRuleMutation) Fields() []string {
func (m *ErrorPassthroughRuleMutation) Fields() []string {
fields := make([]string, 0, 1
4
)
fields := make([]string, 0, 1
5
)
if m.created_at != nil {
if m.created_at != nil {
fields = append(fields, errorpassthroughrule.FieldCreatedAt)
fields = append(fields, errorpassthroughrule.FieldCreatedAt)
}
}
...
@@ -6626,6 +6663,9 @@ func (m *ErrorPassthroughRuleMutation) Fields() []string {
...
@@ -6626,6 +6663,9 @@ func (m *ErrorPassthroughRuleMutation) Fields() []string {
if m.custom_message != nil {
if m.custom_message != nil {
fields = append(fields, errorpassthroughrule.FieldCustomMessage)
fields = append(fields, errorpassthroughrule.FieldCustomMessage)
}
}
if m.skip_monitoring != nil {
fields = append(fields, errorpassthroughrule.FieldSkipMonitoring)
}
if m.description != nil {
if m.description != nil {
fields = append(fields, errorpassthroughrule.FieldDescription)
fields = append(fields, errorpassthroughrule.FieldDescription)
}
}
...
@@ -6663,6 +6703,8 @@ func (m *ErrorPassthroughRuleMutation) Field(name string) (ent.Value, bool) {
...
@@ -6663,6 +6703,8 @@ func (m *ErrorPassthroughRuleMutation) Field(name string) (ent.Value, bool) {
return m.PassthroughBody()
return m.PassthroughBody()
case errorpassthroughrule.FieldCustomMessage:
case errorpassthroughrule.FieldCustomMessage:
return m.CustomMessage()
return m.CustomMessage()
case errorpassthroughrule.FieldSkipMonitoring:
return m.SkipMonitoring()
case errorpassthroughrule.FieldDescription:
case errorpassthroughrule.FieldDescription:
return m.Description()
return m.Description()
}
}
...
@@ -6700,6 +6742,8 @@ func (m *ErrorPassthroughRuleMutation) OldField(ctx context.Context, name string
...
@@ -6700,6 +6742,8 @@ func (m *ErrorPassthroughRuleMutation) OldField(ctx context.Context, name string
return m.OldPassthroughBody(ctx)
return m.OldPassthroughBody(ctx)
case errorpassthroughrule.FieldCustomMessage:
case errorpassthroughrule.FieldCustomMessage:
return m.OldCustomMessage(ctx)
return m.OldCustomMessage(ctx)
case errorpassthroughrule.FieldSkipMonitoring:
return m.OldSkipMonitoring(ctx)
case errorpassthroughrule.FieldDescription:
case errorpassthroughrule.FieldDescription:
return m.OldDescription(ctx)
return m.OldDescription(ctx)
}
}
...
@@ -6802,6 +6846,13 @@ func (m *ErrorPassthroughRuleMutation) SetField(name string, value ent.Value) er
...
@@ -6802,6 +6846,13 @@ func (m *ErrorPassthroughRuleMutation) SetField(name string, value ent.Value) er
}
}
m.SetCustomMessage(v)
m.SetCustomMessage(v)
return nil
return nil
case errorpassthroughrule.FieldSkipMonitoring:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSkipMonitoring(v)
return nil
case errorpassthroughrule.FieldDescription:
case errorpassthroughrule.FieldDescription:
v, ok := value.(string)
v, ok := value.(string)
if !ok {
if !ok {
...
@@ -6963,6 +7014,9 @@ func (m *ErrorPassthroughRuleMutation) ResetField(name string) error {
...
@@ -6963,6 +7014,9 @@ func (m *ErrorPassthroughRuleMutation) ResetField(name string) error {
case errorpassthroughrule.FieldCustomMessage:
case errorpassthroughrule.FieldCustomMessage:
m.ResetCustomMessage()
m.ResetCustomMessage()
return nil
return nil
case errorpassthroughrule.FieldSkipMonitoring:
m.ResetSkipMonitoring()
return nil
case errorpassthroughrule.FieldDescription:
case errorpassthroughrule.FieldDescription:
m.ResetDescription()
m.ResetDescription()
return nil
return nil
...
...
backend/ent/runtime/runtime.go
View file @
8da5fac6
...
@@ -326,6 +326,10 @@ func init() {
...
@@ -326,6 +326,10 @@ func init() {
errorpassthroughruleDescPassthroughBody
:=
errorpassthroughruleFields
[
9
]
.
Descriptor
()
errorpassthroughruleDescPassthroughBody
:=
errorpassthroughruleFields
[
9
]
.
Descriptor
()
// errorpassthroughrule.DefaultPassthroughBody holds the default value on creation for the passthrough_body field.
// errorpassthroughrule.DefaultPassthroughBody holds the default value on creation for the passthrough_body field.
errorpassthroughrule
.
DefaultPassthroughBody
=
errorpassthroughruleDescPassthroughBody
.
Default
.
(
bool
)
errorpassthroughrule
.
DefaultPassthroughBody
=
errorpassthroughruleDescPassthroughBody
.
Default
.
(
bool
)
// errorpassthroughruleDescSkipMonitoring is the schema descriptor for skip_monitoring field.
errorpassthroughruleDescSkipMonitoring
:=
errorpassthroughruleFields
[
11
]
.
Descriptor
()
// errorpassthroughrule.DefaultSkipMonitoring holds the default value on creation for the skip_monitoring field.
errorpassthroughrule
.
DefaultSkipMonitoring
=
errorpassthroughruleDescSkipMonitoring
.
Default
.
(
bool
)
groupMixin
:=
schema
.
Group
{}
.
Mixin
()
groupMixin
:=
schema
.
Group
{}
.
Mixin
()
groupMixinHooks1
:=
groupMixin
[
1
]
.
Hooks
()
groupMixinHooks1
:=
groupMixin
[
1
]
.
Hooks
()
group
.
Hooks
[
0
]
=
groupMixinHooks1
[
0
]
group
.
Hooks
[
0
]
=
groupMixinHooks1
[
0
]
...
...
backend/ent/schema/error_passthrough_rule.go
View file @
8da5fac6
...
@@ -105,6 +105,12 @@ func (ErrorPassthroughRule) Fields() []ent.Field {
...
@@ -105,6 +105,12 @@ func (ErrorPassthroughRule) Fields() []ent.Field {
Optional
()
.
Optional
()
.
Nillable
(),
Nillable
(),
// skip_monitoring: 是否跳过运维监控记录
// true: 匹配此规则的错误不会被记录到 ops_error_logs
// false: 正常记录到运维监控(默认行为)
field
.
Bool
(
"skip_monitoring"
)
.
Default
(
false
),
// description: 规则描述,用于说明规则的用途
// description: 规则描述,用于说明规则的用途
field
.
Text
(
"description"
)
.
field
.
Text
(
"description"
)
.
Optional
()
.
Optional
()
.
...
...
backend/internal/handler/admin/error_passthrough_handler.go
View file @
8da5fac6
...
@@ -32,6 +32,7 @@ type CreateErrorPassthroughRuleRequest struct {
...
@@ -32,6 +32,7 @@ type CreateErrorPassthroughRuleRequest struct {
ResponseCode
*
int
`json:"response_code"`
ResponseCode
*
int
`json:"response_code"`
PassthroughBody
*
bool
`json:"passthrough_body"`
PassthroughBody
*
bool
`json:"passthrough_body"`
CustomMessage
*
string
`json:"custom_message"`
CustomMessage
*
string
`json:"custom_message"`
SkipMonitoring
*
bool
`json:"skip_monitoring"`
Description
*
string
`json:"description"`
Description
*
string
`json:"description"`
}
}
...
@@ -48,6 +49,7 @@ type UpdateErrorPassthroughRuleRequest struct {
...
@@ -48,6 +49,7 @@ type UpdateErrorPassthroughRuleRequest struct {
ResponseCode
*
int
`json:"response_code"`
ResponseCode
*
int
`json:"response_code"`
PassthroughBody
*
bool
`json:"passthrough_body"`
PassthroughBody
*
bool
`json:"passthrough_body"`
CustomMessage
*
string
`json:"custom_message"`
CustomMessage
*
string
`json:"custom_message"`
SkipMonitoring
*
bool
`json:"skip_monitoring"`
Description
*
string
`json:"description"`
Description
*
string
`json:"description"`
}
}
...
@@ -122,6 +124,9 @@ func (h *ErrorPassthroughHandler) Create(c *gin.Context) {
...
@@ -122,6 +124,9 @@ func (h *ErrorPassthroughHandler) Create(c *gin.Context) {
}
else
{
}
else
{
rule
.
PassthroughBody
=
true
rule
.
PassthroughBody
=
true
}
}
if
req
.
SkipMonitoring
!=
nil
{
rule
.
SkipMonitoring
=
*
req
.
SkipMonitoring
}
rule
.
ResponseCode
=
req
.
ResponseCode
rule
.
ResponseCode
=
req
.
ResponseCode
rule
.
CustomMessage
=
req
.
CustomMessage
rule
.
CustomMessage
=
req
.
CustomMessage
rule
.
Description
=
req
.
Description
rule
.
Description
=
req
.
Description
...
@@ -190,6 +195,7 @@ func (h *ErrorPassthroughHandler) Update(c *gin.Context) {
...
@@ -190,6 +195,7 @@ func (h *ErrorPassthroughHandler) Update(c *gin.Context) {
ResponseCode
:
existing
.
ResponseCode
,
ResponseCode
:
existing
.
ResponseCode
,
PassthroughBody
:
existing
.
PassthroughBody
,
PassthroughBody
:
existing
.
PassthroughBody
,
CustomMessage
:
existing
.
CustomMessage
,
CustomMessage
:
existing
.
CustomMessage
,
SkipMonitoring
:
existing
.
SkipMonitoring
,
Description
:
existing
.
Description
,
Description
:
existing
.
Description
,
}
}
...
@@ -230,6 +236,9 @@ func (h *ErrorPassthroughHandler) Update(c *gin.Context) {
...
@@ -230,6 +236,9 @@ func (h *ErrorPassthroughHandler) Update(c *gin.Context) {
if
req
.
Description
!=
nil
{
if
req
.
Description
!=
nil
{
rule
.
Description
=
req
.
Description
rule
.
Description
=
req
.
Description
}
}
if
req
.
SkipMonitoring
!=
nil
{
rule
.
SkipMonitoring
=
*
req
.
SkipMonitoring
}
// 确保切片不为 nil
// 确保切片不为 nil
if
rule
.
ErrorCodes
==
nil
{
if
rule
.
ErrorCodes
==
nil
{
...
...
backend/internal/handler/gateway_handler.go
View file @
8da5fac6
...
@@ -235,6 +235,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -235,6 +235,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
maxAccountSwitches
:=
h
.
maxAccountSwitchesGemini
maxAccountSwitches
:=
h
.
maxAccountSwitchesGemini
switchCount
:=
0
switchCount
:=
0
failedAccountIDs
:=
make
(
map
[
int64
]
struct
{})
failedAccountIDs
:=
make
(
map
[
int64
]
struct
{})
sameAccountRetryCount
:=
make
(
map
[
int64
]
int
)
// 同账号重试计数
var
lastFailoverErr
*
service
.
UpstreamFailoverError
var
lastFailoverErr
*
service
.
UpstreamFailoverError
var
forceCacheBilling
bool
// 粘性会话切换时的缓存计费标记
var
forceCacheBilling
bool
// 粘性会话切换时的缓存计费标记
...
@@ -358,11 +359,28 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -358,11 +359,28 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
if
err
!=
nil
{
if
err
!=
nil
{
var
failoverErr
*
service
.
UpstreamFailoverError
var
failoverErr
*
service
.
UpstreamFailoverError
if
errors
.
As
(
err
,
&
failoverErr
)
{
if
errors
.
As
(
err
,
&
failoverErr
)
{
failedAccountIDs
[
account
.
ID
]
=
struct
{}{}
lastFailoverErr
=
failoverErr
lastFailoverErr
=
failoverErr
if
needForceCacheBilling
(
hasBoundSession
,
failoverErr
)
{
if
needForceCacheBilling
(
hasBoundSession
,
failoverErr
)
{
forceCacheBilling
=
true
forceCacheBilling
=
true
}
}
// 同账号重试:对 RetryableOnSameAccount 的临时性错误,先在同一账号上重试
if
failoverErr
.
RetryableOnSameAccount
&&
sameAccountRetryCount
[
account
.
ID
]
<
maxSameAccountRetries
{
sameAccountRetryCount
[
account
.
ID
]
++
log
.
Printf
(
"Account %d: retryable error %d, same-account retry %d/%d"
,
account
.
ID
,
failoverErr
.
StatusCode
,
sameAccountRetryCount
[
account
.
ID
],
maxSameAccountRetries
)
if
!
sleepSameAccountRetryDelay
(
c
.
Request
.
Context
())
{
return
}
continue
}
// 同账号重试用尽,执行临时封禁并切换账号
if
failoverErr
.
RetryableOnSameAccount
{
h
.
gatewayService
.
TempUnscheduleRetryableError
(
c
.
Request
.
Context
(),
account
.
ID
,
failoverErr
)
}
failedAccountIDs
[
account
.
ID
]
=
struct
{}{}
if
switchCount
>=
maxAccountSwitches
{
if
switchCount
>=
maxAccountSwitches
{
h
.
handleFailoverExhausted
(
c
,
failoverErr
,
service
.
PlatformGemini
,
streamStarted
)
h
.
handleFailoverExhausted
(
c
,
failoverErr
,
service
.
PlatformGemini
,
streamStarted
)
return
return
...
@@ -426,6 +444,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -426,6 +444,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
maxAccountSwitches
:=
h
.
maxAccountSwitches
maxAccountSwitches
:=
h
.
maxAccountSwitches
switchCount
:=
0
switchCount
:=
0
failedAccountIDs
:=
make
(
map
[
int64
]
struct
{})
failedAccountIDs
:=
make
(
map
[
int64
]
struct
{})
sameAccountRetryCount
:=
make
(
map
[
int64
]
int
)
// 同账号重试计数
var
lastFailoverErr
*
service
.
UpstreamFailoverError
var
lastFailoverErr
*
service
.
UpstreamFailoverError
retryWithFallback
:=
false
retryWithFallback
:=
false
var
forceCacheBilling
bool
// 粘性会话切换时的缓存计费标记
var
forceCacheBilling
bool
// 粘性会话切换时的缓存计费标记
...
@@ -579,11 +598,28 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -579,11 +598,28 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
}
}
var
failoverErr
*
service
.
UpstreamFailoverError
var
failoverErr
*
service
.
UpstreamFailoverError
if
errors
.
As
(
err
,
&
failoverErr
)
{
if
errors
.
As
(
err
,
&
failoverErr
)
{
failedAccountIDs
[
account
.
ID
]
=
struct
{}{}
lastFailoverErr
=
failoverErr
lastFailoverErr
=
failoverErr
if
needForceCacheBilling
(
hasBoundSession
,
failoverErr
)
{
if
needForceCacheBilling
(
hasBoundSession
,
failoverErr
)
{
forceCacheBilling
=
true
forceCacheBilling
=
true
}
}
// 同账号重试:对 RetryableOnSameAccount 的临时性错误,先在同一账号上重试
if
failoverErr
.
RetryableOnSameAccount
&&
sameAccountRetryCount
[
account
.
ID
]
<
maxSameAccountRetries
{
sameAccountRetryCount
[
account
.
ID
]
++
log
.
Printf
(
"Account %d: retryable error %d, same-account retry %d/%d"
,
account
.
ID
,
failoverErr
.
StatusCode
,
sameAccountRetryCount
[
account
.
ID
],
maxSameAccountRetries
)
if
!
sleepSameAccountRetryDelay
(
c
.
Request
.
Context
())
{
return
}
continue
}
// 同账号重试用尽,执行临时封禁并切换账号
if
failoverErr
.
RetryableOnSameAccount
{
h
.
gatewayService
.
TempUnscheduleRetryableError
(
c
.
Request
.
Context
(),
account
.
ID
,
failoverErr
)
}
failedAccountIDs
[
account
.
ID
]
=
struct
{}{}
if
switchCount
>=
maxAccountSwitches
{
if
switchCount
>=
maxAccountSwitches
{
h
.
handleFailoverExhausted
(
c
,
failoverErr
,
account
.
Platform
,
streamStarted
)
h
.
handleFailoverExhausted
(
c
,
failoverErr
,
account
.
Platform
,
streamStarted
)
return
return
...
@@ -863,6 +899,23 @@ func needForceCacheBilling(hasBoundSession bool, failoverErr *service.UpstreamFa
...
@@ -863,6 +899,23 @@ func needForceCacheBilling(hasBoundSession bool, failoverErr *service.UpstreamFa
return
hasBoundSession
||
(
failoverErr
!=
nil
&&
failoverErr
.
ForceCacheBilling
)
return
hasBoundSession
||
(
failoverErr
!=
nil
&&
failoverErr
.
ForceCacheBilling
)
}
}
const
(
// maxSameAccountRetries 同账号重试次数上限(针对 RetryableOnSameAccount 错误)
maxSameAccountRetries
=
2
// sameAccountRetryDelay 同账号重试间隔
sameAccountRetryDelay
=
500
*
time
.
Millisecond
)
// sleepSameAccountRetryDelay 同账号重试固定延时,返回 false 表示 context 已取消。
func
sleepSameAccountRetryDelay
(
ctx
context
.
Context
)
bool
{
select
{
case
<-
ctx
.
Done
()
:
return
false
case
<-
time
.
After
(
sameAccountRetryDelay
)
:
return
true
}
}
// sleepFailoverDelay 账号切换线性递增延时:第1次0s、第2次1s、第3次2s…
// sleepFailoverDelay 账号切换线性递增延时:第1次0s、第2次1s、第3次2s…
// 返回 false 表示 context 已取消。
// 返回 false 表示 context 已取消。
func
sleepFailoverDelay
(
ctx
context
.
Context
,
switchCount
int
)
bool
{
func
sleepFailoverDelay
(
ctx
context
.
Context
,
switchCount
int
)
bool
{
...
@@ -918,6 +971,10 @@ func (h *GatewayHandler) handleFailoverExhausted(c *gin.Context, failoverErr *se
...
@@ -918,6 +971,10 @@ func (h *GatewayHandler) handleFailoverExhausted(c *gin.Context, failoverErr *se
msg
=
*
rule
.
CustomMessage
msg
=
*
rule
.
CustomMessage
}
}
if
rule
.
SkipMonitoring
{
c
.
Set
(
service
.
OpsSkipPassthroughKey
,
true
)
}
h
.
handleStreamingAwareError
(
c
,
respCode
,
"upstream_error"
,
msg
,
streamStarted
)
h
.
handleStreamingAwareError
(
c
,
respCode
,
"upstream_error"
,
msg
,
streamStarted
)
return
return
}
}
...
...
backend/internal/handler/gemini_v1beta_handler.go
View file @
8da5fac6
...
@@ -554,6 +554,10 @@ func (h *GatewayHandler) handleGeminiFailoverExhausted(c *gin.Context, failoverE
...
@@ -554,6 +554,10 @@ func (h *GatewayHandler) handleGeminiFailoverExhausted(c *gin.Context, failoverE
msg
=
*
rule
.
CustomMessage
msg
=
*
rule
.
CustomMessage
}
}
if
rule
.
SkipMonitoring
{
c
.
Set
(
service
.
OpsSkipPassthroughKey
,
true
)
}
googleError
(
c
,
respCode
,
msg
)
googleError
(
c
,
respCode
,
msg
)
return
return
}
}
...
...
backend/internal/handler/openai_gateway_handler.go
View file @
8da5fac6
...
@@ -358,6 +358,10 @@ func (h *OpenAIGatewayHandler) handleFailoverExhausted(c *gin.Context, failoverE
...
@@ -358,6 +358,10 @@ func (h *OpenAIGatewayHandler) handleFailoverExhausted(c *gin.Context, failoverE
msg
=
*
rule
.
CustomMessage
msg
=
*
rule
.
CustomMessage
}
}
if
rule
.
SkipMonitoring
{
c
.
Set
(
service
.
OpsSkipPassthroughKey
,
true
)
}
h
.
handleStreamingAwareError
(
c
,
respCode
,
"upstream_error"
,
msg
,
streamStarted
)
h
.
handleStreamingAwareError
(
c
,
respCode
,
"upstream_error"
,
msg
,
streamStarted
)
return
return
}
}
...
...
backend/internal/handler/ops_error_logger.go
View file @
8da5fac6
...
@@ -537,6 +537,13 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc {
...
@@ -537,6 +537,13 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc {
// Store request headers/body only when an upstream error occurred to keep overhead minimal.
// Store request headers/body only when an upstream error occurred to keep overhead minimal.
entry
.
RequestHeadersJSON
=
extractOpsRetryRequestHeaders
(
c
)
entry
.
RequestHeadersJSON
=
extractOpsRetryRequestHeaders
(
c
)
// Skip logging if a passthrough rule with skip_monitoring=true matched.
if
v
,
ok
:=
c
.
Get
(
service
.
OpsSkipPassthroughKey
);
ok
{
if
skip
,
_
:=
v
.
(
bool
);
skip
{
return
}
}
enqueueOpsErrorLog
(
ops
,
entry
,
requestBody
)
enqueueOpsErrorLog
(
ops
,
entry
,
requestBody
)
return
return
}
}
...
@@ -544,6 +551,13 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc {
...
@@ -544,6 +551,13 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc {
body
:=
w
.
buf
.
Bytes
()
body
:=
w
.
buf
.
Bytes
()
parsed
:=
parseOpsErrorResponse
(
body
)
parsed
:=
parseOpsErrorResponse
(
body
)
// Skip logging if a passthrough rule with skip_monitoring=true matched.
if
v
,
ok
:=
c
.
Get
(
service
.
OpsSkipPassthroughKey
);
ok
{
if
skip
,
_
:=
v
.
(
bool
);
skip
{
return
}
}
// Skip logging if the error should be filtered based on settings
// Skip logging if the error should be filtered based on settings
if
shouldSkipOpsErrorLog
(
c
.
Request
.
Context
(),
ops
,
parsed
.
Message
,
string
(
body
),
c
.
Request
.
URL
.
Path
)
{
if
shouldSkipOpsErrorLog
(
c
.
Request
.
Context
(),
ops
,
parsed
.
Message
,
string
(
body
),
c
.
Request
.
URL
.
Path
)
{
return
return
...
...
backend/internal/model/error_passthrough_rule.go
View file @
8da5fac6
...
@@ -18,6 +18,7 @@ type ErrorPassthroughRule struct {
...
@@ -18,6 +18,7 @@ type ErrorPassthroughRule struct {
ResponseCode
*
int
`json:"response_code"`
// 自定义状态码(passthrough_code=false 时使用)
ResponseCode
*
int
`json:"response_code"`
// 自定义状态码(passthrough_code=false 时使用)
PassthroughBody
bool
`json:"passthrough_body"`
// 是否透传原始错误信息
PassthroughBody
bool
`json:"passthrough_body"`
// 是否透传原始错误信息
CustomMessage
*
string
`json:"custom_message"`
// 自定义错误信息(passthrough_body=false 时使用)
CustomMessage
*
string
`json:"custom_message"`
// 自定义错误信息(passthrough_body=false 时使用)
SkipMonitoring
bool
`json:"skip_monitoring"`
// 是否跳过运维监控记录
Description
*
string
`json:"description"`
// 规则描述
Description
*
string
`json:"description"`
// 规则描述
CreatedAt
time
.
Time
`json:"created_at"`
CreatedAt
time
.
Time
`json:"created_at"`
UpdatedAt
time
.
Time
`json:"updated_at"`
UpdatedAt
time
.
Time
`json:"updated_at"`
...
...
backend/internal/pkg/antigravity/client.go
View file @
8da5fac6
...
@@ -115,6 +115,23 @@ type LoadCodeAssistResponse struct {
...
@@ -115,6 +115,23 @@ type LoadCodeAssistResponse struct {
IneligibleTiers
[]
*
IneligibleTier
`json:"ineligibleTiers,omitempty"`
IneligibleTiers
[]
*
IneligibleTier
`json:"ineligibleTiers,omitempty"`
}
}
// OnboardUserRequest onboardUser 请求
type
OnboardUserRequest
struct
{
TierID
string
`json:"tierId"`
Metadata
struct
{
IDEType
string
`json:"ideType"`
Platform
string
`json:"platform,omitempty"`
PluginType
string
`json:"pluginType,omitempty"`
}
`json:"metadata"`
}
// OnboardUserResponse onboardUser 响应
type
OnboardUserResponse
struct
{
Name
string
`json:"name,omitempty"`
Done
bool
`json:"done"`
Response
map
[
string
]
any
`json:"response,omitempty"`
}
// GetTier 获取账户类型
// GetTier 获取账户类型
// 优先返回 paidTier(付费订阅级别),否则返回 currentTier
// 优先返回 paidTier(付费订阅级别),否则返回 currentTier
func
(
r
*
LoadCodeAssistResponse
)
GetTier
()
string
{
func
(
r
*
LoadCodeAssistResponse
)
GetTier
()
string
{
...
@@ -361,6 +378,117 @@ func (c *Client) LoadCodeAssist(ctx context.Context, accessToken string) (*LoadC
...
@@ -361,6 +378,117 @@ func (c *Client) LoadCodeAssist(ctx context.Context, accessToken string) (*LoadC
return
nil
,
nil
,
lastErr
return
nil
,
nil
,
lastErr
}
}
// OnboardUser 触发账号 onboarding,并返回 project_id
// 说明:
// 1) 部分账号 loadCodeAssist 不会立即返回 cloudaicompanionProject;
// 2) 这时需要调用 onboardUser 完成初始化,之后才能拿到 project_id。
func
(
c
*
Client
)
OnboardUser
(
ctx
context
.
Context
,
accessToken
,
tierID
string
)
(
string
,
error
)
{
tierID
=
strings
.
TrimSpace
(
tierID
)
if
tierID
==
""
{
return
""
,
fmt
.
Errorf
(
"tier_id 为空"
)
}
reqBody
:=
OnboardUserRequest
{
TierID
:
tierID
}
reqBody
.
Metadata
.
IDEType
=
"ANTIGRAVITY"
reqBody
.
Metadata
.
Platform
=
"PLATFORM_UNSPECIFIED"
reqBody
.
Metadata
.
PluginType
=
"GEMINI"
bodyBytes
,
err
:=
json
.
Marshal
(
reqBody
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"序列化请求失败: %w"
,
err
)
}
availableURLs
:=
BaseURLs
var
lastErr
error
for
urlIdx
,
baseURL
:=
range
availableURLs
{
apiURL
:=
baseURL
+
"/v1internal:onboardUser"
for
attempt
:=
1
;
attempt
<=
5
;
attempt
++
{
req
,
err
:=
http
.
NewRequestWithContext
(
ctx
,
http
.
MethodPost
,
apiURL
,
bytes
.
NewReader
(
bodyBytes
))
if
err
!=
nil
{
lastErr
=
fmt
.
Errorf
(
"创建请求失败: %w"
,
err
)
break
}
req
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
accessToken
)
req
.
Header
.
Set
(
"Content-Type"
,
"application/json"
)
req
.
Header
.
Set
(
"User-Agent"
,
UserAgent
)
resp
,
err
:=
c
.
httpClient
.
Do
(
req
)
if
err
!=
nil
{
lastErr
=
fmt
.
Errorf
(
"onboardUser 请求失败: %w"
,
err
)
if
shouldFallbackToNextURL
(
err
,
0
)
&&
urlIdx
<
len
(
availableURLs
)
-
1
{
log
.
Printf
(
"[antigravity] onboardUser URL fallback: %s -> %s"
,
baseURL
,
availableURLs
[
urlIdx
+
1
])
break
}
return
""
,
lastErr
}
respBodyBytes
,
err
:=
io
.
ReadAll
(
resp
.
Body
)
_
=
resp
.
Body
.
Close
()
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"读取响应失败: %w"
,
err
)
}
if
shouldFallbackToNextURL
(
nil
,
resp
.
StatusCode
)
&&
urlIdx
<
len
(
availableURLs
)
-
1
{
log
.
Printf
(
"[antigravity] onboardUser URL fallback (HTTP %d): %s -> %s"
,
resp
.
StatusCode
,
baseURL
,
availableURLs
[
urlIdx
+
1
])
break
}
if
resp
.
StatusCode
!=
http
.
StatusOK
{
lastErr
=
fmt
.
Errorf
(
"onboardUser 失败 (HTTP %d): %s"
,
resp
.
StatusCode
,
string
(
respBodyBytes
))
return
""
,
lastErr
}
var
onboardResp
OnboardUserResponse
if
err
:=
json
.
Unmarshal
(
respBodyBytes
,
&
onboardResp
);
err
!=
nil
{
lastErr
=
fmt
.
Errorf
(
"onboardUser 响应解析失败: %w"
,
err
)
return
""
,
lastErr
}
if
onboardResp
.
Done
{
if
projectID
:=
extractProjectIDFromOnboardResponse
(
onboardResp
.
Response
);
projectID
!=
""
{
DefaultURLAvailability
.
MarkSuccess
(
baseURL
)
return
projectID
,
nil
}
lastErr
=
fmt
.
Errorf
(
"onboardUser 完成但未返回 project_id"
)
return
""
,
lastErr
}
// done=false 时等待后重试(与 CLIProxyAPI 行为一致)
select
{
case
<-
time
.
After
(
2
*
time
.
Second
)
:
case
<-
ctx
.
Done
()
:
return
""
,
ctx
.
Err
()
}
}
}
if
lastErr
!=
nil
{
return
""
,
lastErr
}
return
""
,
fmt
.
Errorf
(
"onboardUser 未返回 project_id"
)
}
func
extractProjectIDFromOnboardResponse
(
resp
map
[
string
]
any
)
string
{
if
len
(
resp
)
==
0
{
return
""
}
if
v
,
ok
:=
resp
[
"cloudaicompanionProject"
];
ok
{
switch
project
:=
v
.
(
type
)
{
case
string
:
return
strings
.
TrimSpace
(
project
)
case
map
[
string
]
any
:
if
id
,
ok
:=
project
[
"id"
]
.
(
string
);
ok
{
return
strings
.
TrimSpace
(
id
)
}
}
}
return
""
}
// ModelQuotaInfo 模型配额信息
// ModelQuotaInfo 模型配额信息
type
ModelQuotaInfo
struct
{
type
ModelQuotaInfo
struct
{
RemainingFraction
float64
`json:"remainingFraction"`
RemainingFraction
float64
`json:"remainingFraction"`
...
...
backend/internal/pkg/antigravity/client_test.go
0 → 100644
View file @
8da5fac6
package
antigravity
import
(
"testing"
)
func
TestExtractProjectIDFromOnboardResponse
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tests
:=
[]
struct
{
name
string
resp
map
[
string
]
any
want
string
}{
{
name
:
"nil response"
,
resp
:
nil
,
want
:
""
,
},
{
name
:
"empty response"
,
resp
:
map
[
string
]
any
{},
want
:
""
,
},
{
name
:
"project as string"
,
resp
:
map
[
string
]
any
{
"cloudaicompanionProject"
:
"my-project-123"
,
},
want
:
"my-project-123"
,
},
{
name
:
"project as string with spaces"
,
resp
:
map
[
string
]
any
{
"cloudaicompanionProject"
:
" my-project-123 "
,
},
want
:
"my-project-123"
,
},
{
name
:
"project as map with id"
,
resp
:
map
[
string
]
any
{
"cloudaicompanionProject"
:
map
[
string
]
any
{
"id"
:
"proj-from-map"
,
},
},
want
:
"proj-from-map"
,
},
{
name
:
"project as map without id"
,
resp
:
map
[
string
]
any
{
"cloudaicompanionProject"
:
map
[
string
]
any
{
"name"
:
"some-name"
,
},
},
want
:
""
,
},
{
name
:
"missing cloudaicompanionProject key"
,
resp
:
map
[
string
]
any
{
"otherField"
:
"value"
,
},
want
:
""
,
},
}
for
_
,
tc
:=
range
tests
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
t
.
Parallel
()
got
:=
extractProjectIDFromOnboardResponse
(
tc
.
resp
)
if
got
!=
tc
.
want
{
t
.
Fatalf
(
"extractProjectIDFromOnboardResponse() = %q, want %q"
,
got
,
tc
.
want
)
}
})
}
}
backend/internal/repository/error_passthrough_repo.go
View file @
8da5fac6
...
@@ -54,7 +54,8 @@ func (r *errorPassthroughRepository) Create(ctx context.Context, rule *model.Err
...
@@ -54,7 +54,8 @@ func (r *errorPassthroughRepository) Create(ctx context.Context, rule *model.Err
SetPriority
(
rule
.
Priority
)
.
SetPriority
(
rule
.
Priority
)
.
SetMatchMode
(
rule
.
MatchMode
)
.
SetMatchMode
(
rule
.
MatchMode
)
.
SetPassthroughCode
(
rule
.
PassthroughCode
)
.
SetPassthroughCode
(
rule
.
PassthroughCode
)
.
SetPassthroughBody
(
rule
.
PassthroughBody
)
SetPassthroughBody
(
rule
.
PassthroughBody
)
.
SetSkipMonitoring
(
rule
.
SkipMonitoring
)
if
len
(
rule
.
ErrorCodes
)
>
0
{
if
len
(
rule
.
ErrorCodes
)
>
0
{
builder
.
SetErrorCodes
(
rule
.
ErrorCodes
)
builder
.
SetErrorCodes
(
rule
.
ErrorCodes
)
...
@@ -90,7 +91,8 @@ func (r *errorPassthroughRepository) Update(ctx context.Context, rule *model.Err
...
@@ -90,7 +91,8 @@ func (r *errorPassthroughRepository) Update(ctx context.Context, rule *model.Err
SetPriority
(
rule
.
Priority
)
.
SetPriority
(
rule
.
Priority
)
.
SetMatchMode
(
rule
.
MatchMode
)
.
SetMatchMode
(
rule
.
MatchMode
)
.
SetPassthroughCode
(
rule
.
PassthroughCode
)
.
SetPassthroughCode
(
rule
.
PassthroughCode
)
.
SetPassthroughBody
(
rule
.
PassthroughBody
)
SetPassthroughBody
(
rule
.
PassthroughBody
)
.
SetSkipMonitoring
(
rule
.
SkipMonitoring
)
// 处理可选字段
// 处理可选字段
if
len
(
rule
.
ErrorCodes
)
>
0
{
if
len
(
rule
.
ErrorCodes
)
>
0
{
...
@@ -149,6 +151,7 @@ func (r *errorPassthroughRepository) toModel(e *ent.ErrorPassthroughRule) *model
...
@@ -149,6 +151,7 @@ func (r *errorPassthroughRepository) toModel(e *ent.ErrorPassthroughRule) *model
Platforms
:
e
.
Platforms
,
Platforms
:
e
.
Platforms
,
PassthroughCode
:
e
.
PassthroughCode
,
PassthroughCode
:
e
.
PassthroughCode
,
PassthroughBody
:
e
.
PassthroughBody
,
PassthroughBody
:
e
.
PassthroughBody
,
SkipMonitoring
:
e
.
SkipMonitoring
,
CreatedAt
:
e
.
CreatedAt
,
CreatedAt
:
e
.
CreatedAt
,
UpdatedAt
:
e
.
UpdatedAt
,
UpdatedAt
:
e
.
UpdatedAt
,
}
}
...
...
backend/internal/service/antigravity_gateway_service.go
View file @
8da5fac6
This diff is collapsed.
Click to expand it.
backend/internal/service/antigravity_oauth_service.go
View file @
8da5fac6
...
@@ -273,12 +273,21 @@ func (s *AntigravityOAuthService) loadProjectIDWithRetry(ctx context.Context, ac
...
@@ -273,12 +273,21 @@ func (s *AntigravityOAuthService) loadProjectIDWithRetry(ctx context.Context, ac
}
}
client
:=
antigravity
.
NewClient
(
proxyURL
)
client
:=
antigravity
.
NewClient
(
proxyURL
)
loadResp
,
_
,
err
:=
client
.
LoadCodeAssist
(
ctx
,
accessToken
)
loadResp
,
loadRaw
,
err
:=
client
.
LoadCodeAssist
(
ctx
,
accessToken
)
if
err
==
nil
&&
loadResp
!=
nil
&&
loadResp
.
CloudAICompanionProject
!=
""
{
if
err
==
nil
&&
loadResp
!=
nil
&&
loadResp
.
CloudAICompanionProject
!=
""
{
return
loadResp
.
CloudAICompanionProject
,
nil
return
loadResp
.
CloudAICompanionProject
,
nil
}
}
if
err
==
nil
{
if
projectID
,
onboardErr
:=
tryOnboardProjectID
(
ctx
,
client
,
accessToken
,
loadRaw
);
onboardErr
==
nil
&&
projectID
!=
""
{
return
projectID
,
nil
}
else
if
onboardErr
!=
nil
{
lastErr
=
onboardErr
continue
}
}
// 记录错误
// 记录错误
if
err
!=
nil
{
if
err
!=
nil
{
lastErr
=
err
lastErr
=
err
...
@@ -292,6 +301,65 @@ func (s *AntigravityOAuthService) loadProjectIDWithRetry(ctx context.Context, ac
...
@@ -292,6 +301,65 @@ func (s *AntigravityOAuthService) loadProjectIDWithRetry(ctx context.Context, ac
return
""
,
fmt
.
Errorf
(
"获取 project_id 失败 (重试 %d 次后): %w"
,
maxRetries
,
lastErr
)
return
""
,
fmt
.
Errorf
(
"获取 project_id 失败 (重试 %d 次后): %w"
,
maxRetries
,
lastErr
)
}
}
func
tryOnboardProjectID
(
ctx
context
.
Context
,
client
*
antigravity
.
Client
,
accessToken
string
,
loadRaw
map
[
string
]
any
)
(
string
,
error
)
{
tierID
:=
resolveDefaultTierID
(
loadRaw
)
if
tierID
==
""
{
return
""
,
fmt
.
Errorf
(
"loadCodeAssist 未返回可用的默认 tier"
)
}
projectID
,
err
:=
client
.
OnboardUser
(
ctx
,
accessToken
,
tierID
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"onboardUser 失败 (tier=%s): %w"
,
tierID
,
err
)
}
return
projectID
,
nil
}
func
resolveDefaultTierID
(
loadRaw
map
[
string
]
any
)
string
{
if
len
(
loadRaw
)
==
0
{
return
""
}
rawTiers
,
ok
:=
loadRaw
[
"allowedTiers"
]
if
!
ok
{
return
""
}
tiers
,
ok
:=
rawTiers
.
([]
any
)
if
!
ok
{
return
""
}
for
_
,
rawTier
:=
range
tiers
{
tier
,
ok
:=
rawTier
.
(
map
[
string
]
any
)
if
!
ok
{
continue
}
if
isDefault
,
_
:=
tier
[
"isDefault"
]
.
(
bool
);
!
isDefault
{
continue
}
if
id
,
ok
:=
tier
[
"id"
]
.
(
string
);
ok
{
id
=
strings
.
TrimSpace
(
id
)
if
id
!=
""
{
return
id
}
}
}
return
""
}
// FillProjectID 仅获取 project_id,不刷新 OAuth token
func
(
s
*
AntigravityOAuthService
)
FillProjectID
(
ctx
context
.
Context
,
account
*
Account
,
accessToken
string
)
(
string
,
error
)
{
var
proxyURL
string
if
account
.
ProxyID
!=
nil
{
proxy
,
err
:=
s
.
proxyRepo
.
GetByID
(
ctx
,
*
account
.
ProxyID
)
if
err
==
nil
&&
proxy
!=
nil
{
proxyURL
=
proxy
.
URL
()
}
}
return
s
.
loadProjectIDWithRetry
(
ctx
,
accessToken
,
proxyURL
,
3
)
}
// BuildAccountCredentials 构建账户凭证
// BuildAccountCredentials 构建账户凭证
func
(
s
*
AntigravityOAuthService
)
BuildAccountCredentials
(
tokenInfo
*
AntigravityTokenInfo
)
map
[
string
]
any
{
func
(
s
*
AntigravityOAuthService
)
BuildAccountCredentials
(
tokenInfo
*
AntigravityTokenInfo
)
map
[
string
]
any
{
creds
:=
map
[
string
]
any
{
creds
:=
map
[
string
]
any
{
...
...
Prev
1
2
Next
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