Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/domain/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type BackupAPIToken struct {
Description string `json:"description"`
ProjectSlug string `json:"projectSlug"` // empty = global
IsEnabled bool `json:"isEnabled"`
DevMode bool `json:"devMode"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
}

Expand Down
6 changes: 6 additions & 0 deletions internal/domain/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@ type ProxyRequest struct {

// 使用的 API Token ID,0 表示未使用 Token
APITokenID uint64 `json:"apiTokenID"`

// 是否开发者模式请求(由 Token 开关决定)
DevMode bool `json:"devMode"`
}

type ProxyUpstreamAttempt struct {
Expand Down Expand Up @@ -722,6 +725,9 @@ type APIToken struct {
// 是否启用
IsEnabled bool `json:"isEnabled"`

// 开发者模式(开启时该令牌请求详情永久保留)
DevMode bool `json:"devMode"`

// 过期时间,nil 表示永不过期
ExpiresAt *time.Time `json:"expiresAt,omitempty"`

Expand Down
21 changes: 17 additions & 4 deletions internal/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,12 @@ func (e *Executor) processAdapterEvents(eventChan domain.AdapterEventChan, attem

// processAdapterEventsRealtime processes events in real-time during adapter execution
// It broadcasts updates immediately when RequestInfo/ResponseInfo are received
func (e *Executor) processAdapterEventsRealtime(eventChan domain.AdapterEventChan, attempt *domain.ProxyUpstreamAttempt, done chan struct{}) {
func (e *Executor) processAdapterEventsRealtime(
eventChan domain.AdapterEventChan,
attempt *domain.ProxyUpstreamAttempt,
done chan struct{},
clearDetail bool,
) {
defer close(done)

if eventChan == nil || attempt == nil {
Expand Down Expand Up @@ -352,12 +357,12 @@ func (e *Executor) processAdapterEventsRealtime(eventChan domain.AdapterEventCha

switch ev.Type {
case domain.EventRequestInfo:
if !e.shouldClearRequestDetail() && ev.RequestInfo != nil {
if !clearDetail && ev.RequestInfo != nil {
attempt.RequestInfo = ev.RequestInfo
dirty = true
}
case domain.EventResponseInfo:
if !e.shouldClearRequestDetail() && ev.ResponseInfo != nil {
if !clearDetail && ev.ResponseInfo != nil {
attempt.ResponseInfo = ev.ResponseInfo
dirty = true
}
Expand Down Expand Up @@ -407,7 +412,15 @@ func (e *Executor) getRequestDetailRetentionSeconds() int {
return seconds
}

// shouldClearRequestDetail 检查是否应该立即清理请求详情
// shouldClearRequestDetailFor 检查是否应该立即清理请求详情(考虑 Token 开发者模式)
func (e *Executor) shouldClearRequestDetailFor(state *execState) bool {
if state != nil && state.apiTokenDevMode {
return false
}
return e.shouldClearRequestDetail()
}

// shouldClearRequestDetail 检查是否应该立即清理请求详情(全局配置)
// 当设置为 0 时返回 true
func (e *Executor) shouldClearRequestDetail() bool {
return e.getRequestDetailRetentionSeconds() == 0
Expand Down
1 change: 1 addition & 0 deletions internal/executor/flow_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type execState struct {
requestModel string
isStream bool
apiTokenID uint64
apiTokenDevMode bool
requestBody []byte
originalRequestBody []byte
requestHeaders http.Header
Expand Down
33 changes: 20 additions & 13 deletions internal/executor/middleware_dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {

proxyReq := state.proxyReq
ctx := state.ctx
clearDetail := e.shouldClearRequestDetailFor(state)

for _, matchedRoute := range state.routes {
if ctx.Err() != nil {
Expand Down Expand Up @@ -127,7 +128,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
c.Set(flow.KeyEventChan, eventChan)
c.Set(flow.KeyBroadcaster, e.broadcaster)
eventDone := make(chan struct{})
go e.processAdapterEventsRealtime(eventChan, attemptRecord, eventDone)
go e.processAdapterEventsRealtime(eventChan, attemptRecord, eventDone, clearDetail)

var responseWriter http.ResponseWriter
var convertingWriter *ConvertingResponseWriter
Expand Down Expand Up @@ -179,7 +180,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
attemptRecord.Multiplier = result.Multiplier
}

if e.shouldClearRequestDetail() {
if clearDetail {
attemptRecord.RequestInfo = nil
attemptRecord.ResponseInfo = nil
}
Expand All @@ -200,7 +201,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
proxyReq.Multiplier = attemptRecord.Multiplier
proxyReq.ResponseModel = mappedModel

if !e.shouldClearRequestDetail() {
if !clearDetail {
proxyReq.ResponseInfo = &domain.ResponseInfo{
Status: responseCapture.StatusCode(),
Headers: responseCapture.CapturedHeaders(),
Expand All @@ -220,10 +221,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
proxyReq.Cost = attemptRecord.Cost
proxyReq.TTFT = attemptRecord.TTFT

if e.shouldClearRequestDetail() {
proxyReq.RequestInfo = nil
proxyReq.ResponseInfo = nil
}
clearProxyRequestDetail(proxyReq, clearDetail)

_ = e.proxyRequestRepo.Update(proxyReq)
if e.broadcaster != nil {
Expand Down Expand Up @@ -265,7 +263,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
attemptRecord.Multiplier = result.Multiplier
}

if e.shouldClearRequestDetail() {
if clearDetail {
attemptRecord.RequestInfo = nil
attemptRecord.ResponseInfo = nil
}
Expand All @@ -282,7 +280,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {

if responseCapture.Body() != "" {
proxyReq.StatusCode = responseCapture.StatusCode()
if !e.shouldClearRequestDetail() {
if !clearDetail {
proxyReq.ResponseInfo = &domain.ResponseInfo{
Status: responseCapture.StatusCode(),
Headers: responseCapture.CapturedHeaders(),
Expand All @@ -301,6 +299,8 @@ func (e *Executor) dispatch(c *flow.Ctx) {
proxyReq.Cost = attemptRecord.Cost
proxyReq.TTFT = attemptRecord.TTFT

clearProxyRequestDetail(proxyReq, clearDetail)

_ = e.proxyRequestRepo.Update(proxyReq)
if e.broadcaster != nil {
e.broadcaster.BroadcastProxyRequest(proxyReq)
Expand All @@ -318,6 +318,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
} else {
proxyReq.Error = ctx.Err().Error()
}
clearProxyRequestDetail(proxyReq, clearDetail)
_ = e.proxyRequestRepo.Update(proxyReq)
if e.broadcaster != nil {
e.broadcaster.BroadcastProxyRequest(proxyReq)
Expand Down Expand Up @@ -365,6 +366,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
} else {
proxyReq.Error = ctx.Err().Error()
}
clearProxyRequestDetail(proxyReq, clearDetail)
_ = e.proxyRequestRepo.Update(proxyReq)
if e.broadcaster != nil {
e.broadcaster.BroadcastProxyRequest(proxyReq)
Expand All @@ -384,10 +386,7 @@ func (e *Executor) dispatch(c *flow.Ctx) {
if state.lastErr != nil {
proxyReq.Error = state.lastErr.Error()
}
if e.shouldClearRequestDetail() {
proxyReq.RequestInfo = nil
proxyReq.ResponseInfo = nil
}
clearProxyRequestDetail(proxyReq, clearDetail)
_ = e.proxyRequestRepo.Update(proxyReq)
if e.broadcaster != nil {
e.broadcaster.BroadcastProxyRequest(proxyReq)
Expand All @@ -399,3 +398,11 @@ func (e *Executor) dispatch(c *flow.Ctx) {
state.ctx = ctx
c.Err = state.lastErr
}

func clearProxyRequestDetail(req *domain.ProxyRequest, clearDetail bool) {
if !clearDetail || req == nil {
return
}
req.RequestInfo = nil
req.ResponseInfo = nil
}
9 changes: 8 additions & 1 deletion internal/executor/middleware_ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ func (e *Executor) ingress(c *flow.Ctx) {
state.apiTokenID = id
}
}
if v, ok := c.Get(flow.KeyAPITokenDevMode); ok {
if devMode, ok := v.(bool); ok {
state.apiTokenDevMode = devMode
}
}
if v, ok := c.Get(flow.KeyRequestBody); ok {
if body, ok := v.([]byte); ok {
state.requestBody = body
Expand Down Expand Up @@ -86,9 +91,11 @@ func (e *Executor) ingress(c *flow.Ctx) {
IsStream: state.isStream,
Status: "PENDING",
APITokenID: state.apiTokenID,
DevMode: state.apiTokenDevMode,
}

if !e.shouldClearRequestDetail() {
clearDetail := e.shouldClearRequestDetailFor(state)
if !clearDetail {
requestURI := state.requestURI
requestHeaders := state.requestHeaders
requestBody := state.requestBody
Expand Down
1 change: 1 addition & 0 deletions internal/flow/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
KeyRequestURI = "request_uri"
KeyIsStream = "is_stream"
KeyAPITokenID = "api_token_id"
KeyAPITokenDevMode = "api_token_dev_mode"
KeyProxyRequest = "proxy_request"
KeyUpstreamAttempt = "upstream_attempt"
KeyEventChan = "event_chan"
Expand Down
4 changes: 4 additions & 0 deletions internal/handler/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,7 @@ func (h *AdminHandler) handleAPITokens(w http.ResponseWriter, r *http.Request, i
Description *string `json:"description"`
ProjectID *uint64 `json:"projectID"`
IsEnabled *bool `json:"isEnabled"`
DevMode *bool `json:"devMode"`
ExpiresAt *string `json:"expiresAt"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
Expand All @@ -1091,6 +1092,9 @@ func (h *AdminHandler) handleAPITokens(w http.ResponseWriter, r *http.Request, i
if body.IsEnabled != nil {
existing.IsEnabled = *body.IsEnabled
}
if body.DevMode != nil {
existing.DevMode = *body.DevMode
}
if body.ExpiresAt != nil {
if *body.ExpiresAt == "" {
existing.ExpiresAt = nil
Expand Down
1 change: 1 addition & 0 deletions internal/handler/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func (h *ProxyHandler) ingress(c *flow.Ctx) {
if apiToken != nil {
apiTokenID = apiToken.ID
log.Printf("[Proxy] Token authenticated: id=%d, name=%s, projectID=%d", apiToken.ID, apiToken.Name, apiToken.ProjectID)
c.Set(flow.KeyAPITokenDevMode, apiToken.DevMode)
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/repository/sqlite/api_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func (r *APITokenRepository) Update(t *domain.APIToken) error {
"description": LongText(t.Description),
"project_id": t.ProjectID,
"is_enabled": boolToInt(t.IsEnabled),
"dev_mode": boolToInt(t.DevMode),
"expires_at": toTimestampPtr(t.ExpiresAt),
}).Error
}
Expand Down Expand Up @@ -115,6 +116,7 @@ func (r *APITokenRepository) toModel(t *domain.APIToken) *APIToken {
Description: LongText(t.Description),
ProjectID: t.ProjectID,
IsEnabled: boolToInt(t.IsEnabled),
DevMode: boolToInt(t.DevMode),
ExpiresAt: toTimestampPtr(t.ExpiresAt),
LastUsedAt: toTimestampPtr(t.LastUsedAt),
UseCount: t.UseCount,
Expand All @@ -133,6 +135,7 @@ func (r *APITokenRepository) toDomain(m *APIToken) *domain.APIToken {
Description: string(m.Description),
ProjectID: m.ProjectID,
IsEnabled: m.IsEnabled == 1,
DevMode: m.DevMode == 1,
ExpiresAt: fromTimestampPtr(m.ExpiresAt),
LastUsedAt: fromTimestampPtr(m.LastUsedAt),
UseCount: m.UseCount,
Expand Down
2 changes: 2 additions & 0 deletions internal/repository/sqlite/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type APIToken struct {
Description LongText
ProjectID uint64
IsEnabled int `gorm:"default:1"`
DevMode int `gorm:"default:0"`
ExpiresAt int64
LastUsedAt int64
UseCount uint64
Expand Down Expand Up @@ -227,6 +228,7 @@ type ProxyRequest struct {
StatusCode int
ProjectID uint64
APITokenID uint64
DevMode int `gorm:"default:0"`
}

func (ProxyRequest) TableName() string { return "proxy_requests" }
Expand Down
4 changes: 3 additions & 1 deletion internal/repository/sqlite/proxy_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ func (r *ProxyRequestRepository) ClearDetailOlderThan(before time.Time) (int64,
now := time.Now().UnixMilli()

result := r.db.gorm.Model(&ProxyRequest{}).
Where("created_at < ? AND (request_info IS NOT NULL OR response_info IS NOT NULL)", beforeTs).
Where("created_at < ? AND (request_info IS NOT NULL OR response_info IS NOT NULL) AND dev_mode = 0", beforeTs).
Updates(map[string]any{
"request_info": nil,
"response_info": nil,
Expand Down Expand Up @@ -474,6 +474,7 @@ func (r *ProxyRequestRepository) toModel(p *domain.ProxyRequest) *ProxyRequest {
Multiplier: p.Multiplier,
Cost: p.Cost,
APITokenID: p.APITokenID,
DevMode: boolToInt(p.DevMode),
}
}

Expand Down Expand Up @@ -513,6 +514,7 @@ func (r *ProxyRequestRepository) toDomain(m *ProxyRequest) *domain.ProxyRequest
Multiplier: m.Multiplier,
Cost: m.Cost,
APITokenID: m.APITokenID,
DevMode: m.DevMode == 1,
}
}

Expand Down
5 changes: 5 additions & 0 deletions internal/repository/sqlite/proxy_upstream_attempt.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,13 @@ func (r *ProxyUpstreamAttemptRepository) ClearDetailOlderThan(before time.Time)
beforeTs := toTimestamp(before)
now := time.Now().UnixMilli()

devModeOffRequests := r.db.gorm.Model(&ProxyRequest{}).
Select("id").
Where("dev_mode = 0")

result := r.db.gorm.Model(&ProxyUpstreamAttempt{}).
Where("created_at < ? AND (request_info IS NOT NULL OR response_info IS NOT NULL)", beforeTs).
Where("proxy_request_id IN (?)", devModeOffRequests).
Updates(map[string]any{
"request_info": nil,
"response_info": nil,
Expand Down
2 changes: 2 additions & 0 deletions internal/service/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ func (s *BackupService) Export() (*domain.BackupFile, error) {
Description: t.Description,
ProjectSlug: projectIDToSlug[t.ProjectID],
IsEnabled: t.IsEnabled,
DevMode: t.DevMode,
ExpiresAt: t.ExpiresAt,
})
}
Expand Down Expand Up @@ -739,6 +740,7 @@ func (s *BackupService) importAPITokens(tokens []domain.BackupAPIToken, opts dom
Description: bt.Description,
ProjectID: projectID,
IsEnabled: bt.IsEnabled,
DevMode: bt.DevMode,
ExpiresAt: bt.ExpiresAt,
}

Expand Down
2 changes: 2 additions & 0 deletions web/src/lib/transport/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ export interface APIToken {
description: string;
projectID: number;
isEnabled: boolean;
devMode: boolean;
expiresAt?: string;
lastUsedAt?: string;
useCount: number;
Expand Down Expand Up @@ -804,6 +805,7 @@ export interface BackupAPIToken {
description: string;
projectSlug: string;
isEnabled: boolean;
devMode?: boolean;
expiresAt?: string;
}

Expand Down
3 changes: 3 additions & 0 deletions web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,9 @@
"enableAuthPrompt": "Enable API Token Authentication to require valid tokens for proxy requests. This adds an extra layer of security by ensuring only authorized clients can access the proxy.",
"tokenName": "Name",
"tokenPrefix": "Token Prefix",
"devMode": "Dev Mode",
"devModeEnabled": "Dev Mode",
"devModeDisabled": "Normal",
"project": "Project",
"usage": "Usage",
"lastUsed": "Last Used",
Expand Down
3 changes: 3 additions & 0 deletions web/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,9 @@
"enableAuthPrompt": "启用 API 令牌身份验证以要求代理请求使用有效令牌。这通过确保只有授权客户端可以访问代理来增加额外的安全层。",
"tokenName": "名称",
"tokenPrefix": "令牌前缀",
"devMode": "开发者模式",
"devModeEnabled": "开发者模式",
"devModeDisabled": "普通模式",
"project": "项目",
"usage": "使用次数",
"lastUsed": "最后使用",
Expand Down
Loading