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
8df662d0
Commit
8df662d0
authored
Dec 31, 2025
by
shaw
Browse files
Merge PR #105: fix(数据层): 修复软删除与唯一约束冲突问题 和 添加 model 参数必填验证
parents
81213f23
2d22623b
Changes
7
Show whitespace changes
Inline
Side-by-side
backend/ent/migrate/schema.go
View file @
8df662d0
...
...
@@ -204,7 +204,7 @@ var (
{
Name
:
"created_at"
,
Type
:
field
.
TypeTime
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"timestamptz"
}},
{
Name
:
"updated_at"
,
Type
:
field
.
TypeTime
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"timestamptz"
}},
{
Name
:
"deleted_at"
,
Type
:
field
.
TypeTime
,
Nullable
:
true
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"timestamptz"
}},
{
Name
:
"name"
,
Type
:
field
.
TypeString
,
Unique
:
true
,
Size
:
100
},
{
Name
:
"name"
,
Type
:
field
.
TypeString
,
Size
:
100
},
{
Name
:
"description"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"text"
}},
{
Name
:
"rate_multiplier"
,
Type
:
field
.
TypeFloat64
,
Default
:
1
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"decimal(10,4)"
}},
{
Name
:
"is_exclusive"
,
Type
:
field
.
TypeBool
,
Default
:
false
},
...
...
@@ -470,7 +470,7 @@ var (
{
Name
:
"created_at"
,
Type
:
field
.
TypeTime
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"timestamptz"
}},
{
Name
:
"updated_at"
,
Type
:
field
.
TypeTime
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"timestamptz"
}},
{
Name
:
"deleted_at"
,
Type
:
field
.
TypeTime
,
Nullable
:
true
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"timestamptz"
}},
{
Name
:
"email"
,
Type
:
field
.
TypeString
,
Unique
:
true
,
Size
:
255
},
{
Name
:
"email"
,
Type
:
field
.
TypeString
,
Size
:
255
},
{
Name
:
"password_hash"
,
Type
:
field
.
TypeString
,
Size
:
255
},
{
Name
:
"role"
,
Type
:
field
.
TypeString
,
Size
:
20
,
Default
:
"user"
},
{
Name
:
"balance"
,
Type
:
field
.
TypeFloat64
,
Default
:
0
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"decimal(20,8)"
}},
...
...
@@ -605,7 +605,7 @@ var (
},
{
Name
:
"usersubscription_user_id_group_id"
,
Unique
:
tru
e
,
Unique
:
fals
e
,
Columns
:
[]
*
schema
.
Column
{
UserSubscriptionsColumns
[
16
],
UserSubscriptionsColumns
[
15
]},
},
{
...
...
backend/ent/schema/group.go
View file @
8df662d0
...
...
@@ -33,10 +33,11 @@ func (Group) Mixin() []ent.Mixin {
func
(
Group
)
Fields
()
[]
ent
.
Field
{
return
[]
ent
.
Field
{
// 唯一约束通过部分索引实现(WHERE deleted_at IS NULL),支持软删除后重用
// 见迁移文件 016_soft_delete_partial_unique_indexes.sql
field
.
String
(
"name"
)
.
MaxLen
(
100
)
.
NotEmpty
()
.
Unique
(),
NotEmpty
(),
field
.
String
(
"description"
)
.
Optional
()
.
Nillable
()
.
...
...
backend/ent/schema/user.go
View file @
8df662d0
...
...
@@ -33,10 +33,11 @@ func (User) Mixin() []ent.Mixin {
func
(
User
)
Fields
()
[]
ent
.
Field
{
return
[]
ent
.
Field
{
// 唯一约束通过部分索引实现(WHERE deleted_at IS NULL),支持软删除后重用
// 见迁移文件 016_soft_delete_partial_unique_indexes.sql
field
.
String
(
"email"
)
.
MaxLen
(
255
)
.
NotEmpty
()
.
Unique
(),
NotEmpty
(),
field
.
String
(
"password_hash"
)
.
MaxLen
(
255
)
.
NotEmpty
(),
...
...
backend/ent/schema/user_subscription.go
View file @
8df662d0
...
...
@@ -109,7 +109,9 @@ func (UserSubscription) Indexes() []ent.Index {
index
.
Fields
(
"status"
),
index
.
Fields
(
"expires_at"
),
index
.
Fields
(
"assigned_by"
),
index
.
Fields
(
"user_id"
,
"group_id"
)
.
Unique
(),
// 唯一约束通过部分索引实现(WHERE deleted_at IS NULL),支持软删除后重新订阅
// 见迁移文件 016_soft_delete_partial_unique_indexes.sql
index
.
Fields
(
"user_id"
,
"group_id"
),
index
.
Fields
(
"deleted_at"
),
}
}
backend/internal/handler/gateway_handler.go
View file @
8df662d0
...
...
@@ -88,6 +88,12 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
reqModel
:=
parsedReq
.
Model
reqStream
:=
parsedReq
.
Stream
// 验证 model 必填
if
reqModel
==
""
{
h
.
errorResponse
(
c
,
http
.
StatusBadRequest
,
"invalid_request_error"
,
"model is required"
)
return
}
// Track if we've started streaming (for error handling)
streamStarted
:=
false
...
...
@@ -517,6 +523,12 @@ func (h *GatewayHandler) CountTokens(c *gin.Context) {
return
}
// 验证 model 必填
if
parsedReq
.
Model
==
""
{
h
.
errorResponse
(
c
,
http
.
StatusBadRequest
,
"invalid_request_error"
,
"model is required"
)
return
}
// 获取订阅信息(可能为nil)
subscription
,
_
:=
middleware2
.
GetSubscriptionFromContext
(
c
)
...
...
backend/internal/handler/openai_gateway_handler.go
View file @
8df662d0
...
...
@@ -80,6 +80,12 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
reqModel
,
_
:=
reqBody
[
"model"
]
.
(
string
)
reqStream
,
_
:=
reqBody
[
"stream"
]
.
(
bool
)
// 验证 model 必填
if
reqModel
==
""
{
h
.
errorResponse
(
c
,
http
.
StatusBadRequest
,
"invalid_request_error"
,
"model is required"
)
return
}
// For non-Codex CLI requests, set default instructions
userAgent
:=
c
.
GetHeader
(
"User-Agent"
)
if
!
openai
.
IsCodexCLIRequest
(
userAgent
)
{
...
...
backend/migrations/016_soft_delete_partial_unique_indexes.sql
0 → 100644
View file @
8df662d0
-- 016_soft_delete_partial_unique_indexes.sql
-- 修复软删除 + 唯一约束冲突问题
-- 将普通唯一约束替换为部分唯一索引(WHERE deleted_at IS NULL)
-- 这样软删除的记录不会占用唯一约束位置,允许删后重建同名/同邮箱/同订阅关系
-- ============================================================================
-- 1. users 表: email 字段
-- ============================================================================
-- 删除旧的唯一约束(可能的命名方式)
ALTER
TABLE
users
DROP
CONSTRAINT
IF
EXISTS
users_email_key
;
DROP
INDEX
IF
EXISTS
users_email_key
;
DROP
INDEX
IF
EXISTS
user_email_key
;
-- 创建部分唯一索引:只对未删除的记录建立唯一约束
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
users_email_unique_active
ON
users
(
email
)
WHERE
deleted_at
IS
NULL
;
-- ============================================================================
-- 2. groups 表: name 字段
-- ============================================================================
-- 删除旧的唯一约束
ALTER
TABLE
groups
DROP
CONSTRAINT
IF
EXISTS
groups_name_key
;
DROP
INDEX
IF
EXISTS
groups_name_key
;
DROP
INDEX
IF
EXISTS
group_name_key
;
-- 创建部分唯一索引
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
groups_name_unique_active
ON
groups
(
name
)
WHERE
deleted_at
IS
NULL
;
-- ============================================================================
-- 3. user_subscriptions 表: (user_id, group_id) 组合字段
-- ============================================================================
-- 删除旧的唯一约束/索引
ALTER
TABLE
user_subscriptions
DROP
CONSTRAINT
IF
EXISTS
user_subscriptions_user_id_group_id_key
;
DROP
INDEX
IF
EXISTS
user_subscriptions_user_id_group_id_key
;
DROP
INDEX
IF
EXISTS
usersubscription_user_id_group_id
;
-- 创建部分唯一索引
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
user_subscriptions_user_group_unique_active
ON
user_subscriptions
(
user_id
,
group_id
)
WHERE
deleted_at
IS
NULL
;
-- ============================================================================
-- 注意: api_keys 表的 key 字段保留普通唯一约束
-- API Key 即使软删除后也不应该重复使用(安全考虑)
-- ============================================================================
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