From f111b363891463ef4ba7c6362851606987d892d5 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Mon, 23 Mar 2026 06:58:28 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20JSON=5FCONTAINS=20=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=BF=AE=E5=A4=8D=20+=20API=20sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 JSON_CONTAINS(node_group_ids, int64) 类型错误,改为传 JSON 字符串 - 添加 node_group_ids IS NOT NULL 兼容判断,防止 NULL 列报错 - 同步 apis/ 及 routes.go、compat_types.go 改动 Co-Authored-By: claude-flow --- apis/admin/marketing.api | 4 ++ apis/auth/auth.api | 10 ++--- apis/common.api | 62 +++++++++++++++++++++++++++++++ apis/public/subscribe.api | 4 ++ apis/public/user.api | 22 ++++++++++- internal/handler/routes.go | 23 ++++++++++-- internal/model/subscribe/model.go | 11 ++++-- internal/types/compat_types.go | 8 ++++ 8 files changed, 131 insertions(+), 13 deletions(-) diff --git a/apis/admin/marketing.api b/apis/admin/marketing.api index 8014c0d..5a18fb8 100644 --- a/apis/admin/marketing.api +++ b/apis/admin/marketing.api @@ -163,5 +163,9 @@ service ppanel { @doc "Query quota task list" @handler QueryQuotaTaskList get /quota/list (QueryQuotaTaskListRequest) returns (QueryQuotaTaskListResponse) + + @doc "Query quota task status" + @handler QueryQuotaTaskStatus + post /quota/status (QueryQuotaTaskStatusRequest) returns (QueryQuotaTaskStatusResponse) } diff --git a/apis/auth/auth.api b/apis/auth/auth.api index fa5d8dd..5e64a75 100644 --- a/apis/auth/auth.api +++ b/apis/auth/auth.api @@ -58,14 +58,14 @@ type ( } // Email login request EmailLoginRequest { - Identifier string `json:"identifier"` - Email string `json:"email" validate:"required"` - Code string `json:"code" validate:"required"` - Invite string `json:"invite,optional"` + Identifier string `json:"identifier" form:"identifier"` + Email string `json:"email" form:"email" validate:"required,email"` + Code string `json:"code" form:"code" validate:"required"` + Invite string `json:"invite,optional" form:"invite"` IP string `header:"X-Original-Forwarded-For"` UserAgent string `header:"User-Agent"` LoginType string `header:"Login-Type"` - CfToken string `json:"cf_token,optional"` + CfToken string `json:"cf_token,optional" form:"cf_token"` } LoginResponse { Token string `json:"token"` diff --git a/apis/common.api b/apis/common.api index 3ece8ac..3702523 100644 --- a/apis/common.api +++ b/apis/common.api @@ -96,6 +96,48 @@ type ( Message string `json:"message,omitempty"` Timestamp int64 `json:"timestamp,omitempty"` } + GetDownloadLinkRequest { + InviteCode string `form:"invite_code,optional"` + Platform string `form:"platform" validate:"required,oneof=windows mac ios android"` + } + GetDownloadLinkResponse { + Url string `json:"url"` + } + ContactRequest { + Name string `json:"name" validate:"required,max=100"` + Email string `json:"email" validate:"required,email"` + OtherContact string `json:"other_contact" validate:"max=200"` + Notes string `json:"notes" validate:"max=2000"` + } + ReportLogMessageRequest { + Platform string `json:"platform" validate:"required,max=32"` + AppVersion string `json:"app_version" validate:"required,max=64"` + OsName string `json:"os_name" validate:"max=64"` + OsVersion string `json:"os_version" validate:"max=64"` + DeviceId string `json:"device_id" validate:"required,max=255"` + UserId int64 `json:"user_id"` + SessionId string `json:"session_id" validate:"max=255"` + Level uint8 `json:"level"` + ErrorCode string `json:"error_code" validate:"max=128"` + Message string `json:"message" validate:"required,max=65535"` + Stack string `json:"stack" validate:"max=1048576"` + Context interface{} `json:"context"` + OccurredAt int64 `json:"occurred_at"` + } + ReportLogMessageResponse { + Id int64 `json:"id"` + } + LegacyCheckVerificationCodeRequest { + Method string `json:"method" form:"method" validate:"omitempty,oneof=email mobile"` + Account string `json:"account" form:"account"` + Email string `json:"email" form:"email"` + Code string `json:"code" form:"code" validate:"required"` + Type uint8 `json:"type" form:"type" validate:"required"` + } + LegacyCheckVerificationCodeResponse { + Status bool `json:"status"` + Exist bool `json:"exist"` + } ) @server ( @@ -143,5 +185,25 @@ service ppanel { @doc "Heartbeat" @handler Heartbeat get /heartbeat returns (HeartbeatResponse) + + @doc "Get Download Link" + @handler GetDownloadLink + get /client/download (GetDownloadLinkRequest) returns (GetDownloadLinkResponse) + + @doc "Submit Contact" + @handler SubmitContact + post /contact (ContactRequest) + + @doc "Report log message" + @handler ReportLogMessage + post /log/report (ReportLogMessageRequest) returns (ReportLogMessageResponse) + + @doc "Check verification code (legacy v1)" + @handler CheckCodeLegacy + post /check_code (LegacyCheckVerificationCodeRequest) returns (LegacyCheckVerificationCodeResponse) + + @doc "Check verification code (legacy v2, consume code)" + @handler CheckCodeLegacyV2 + post /check_code/v2 (LegacyCheckVerificationCodeRequest) returns (LegacyCheckVerificationCodeResponse) } diff --git a/apis/public/subscribe.api b/apis/public/subscribe.api index 445379b..9abba4f 100644 --- a/apis/public/subscribe.api +++ b/apis/public/subscribe.api @@ -71,5 +71,9 @@ service ppanel { @doc "Get user subscribe node info" @handler QueryUserSubscribeNodeList get /node/list returns (QueryUserSubscribeNodeListResponse) + + @doc "Get subscribe group list" + @handler QuerySubscribeGroupList + get /group/list returns (QuerySubscribeGroupListResponse) } diff --git a/apis/public/user.api b/apis/public/user.api index 498bbcb..e55919f 100644 --- a/apis/public/user.api +++ b/apis/public/user.api @@ -220,6 +220,22 @@ type ( FriendlyCount int64 `json:"friendly_count"` HistoryCount int64 `json:"history_count"` } + GetUserTrafficStatsRequest { + UserSubscribeId string `form:"user_subscribe_id" validate:"required"` + Days int `form:"days" validate:"required,oneof=7 30"` + } + DailyTrafficStats { + Date string `json:"date"` + Upload int64 `json:"upload"` + Download int64 `json:"download"` + Total int64 `json:"total"` + } + GetUserTrafficStatsResponse { + List []DailyTrafficStats `json:"list"` + TotalUpload int64 `json:"total_upload"` + TotalDownload int64 `json:"total_download"` + TotalTraffic int64 `json:"total_traffic"` + } ) @server ( @@ -374,11 +390,15 @@ service ppanel { @doc "Get Subscribe Status" @handler GetSubscribeStatus - get /subscribe_status (GetSubscribeStatusRequest) returns (GetSubscribeStatusResponse) + post /subscribe_status (GetSubscribeStatusRequest) returns (GetSubscribeStatusResponse) @doc "Get User Invite Stats" @handler GetUserInviteStats get /invite_stats (GetUserInviteStatsRequest) returns (GetUserInviteStatsResponse) + + @doc "Get User Traffic Statistics" + @handler GetUserTrafficStats + get /traffic_stats (GetUserTrafficStatsRequest) returns (GetUserTrafficStatsResponse) } @server ( diff --git a/internal/handler/routes.go b/internal/handler/routes.go index 9168e0f..48a7dcd 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -321,6 +321,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Query quota task pre-count adminMarketingGroupRouter.POST("/quota/pre-count", adminMarketing.QueryQuotaTaskPreCountHandler(serverCtx)) + + // Query quota task status + adminMarketingGroupRouter.POST("/quota/status", adminMarketing.QueryQuotaTaskStatusHandler(serverCtx)) } adminOrderGroupRouter := router.Group("/v1/admin/order") @@ -808,6 +811,15 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Submit contact info commonGroupRouter.POST("/contact", common.SubmitContactHandler(serverCtx)) + + // Report log message + commonGroupRouter.POST("/log/report", common.ReportLogMessageHandler(serverCtx)) + + // Check verification code (legacy v1) + commonGroupRouter.POST("/check_code", auth.CheckCodeLegacyV1Handler(serverCtx)) + + // Check verification code (legacy v2, consume code) + commonGroupRouter.POST("/check_code/v2", auth.CheckCodeLegacyV2Handler(serverCtx)) } publicAnnouncementGroupRouter := router.Group("/v1/public/announcement") @@ -923,6 +935,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Get user subscribe node info publicSubscribeGroupRouter.GET("/node/list", publicSubscribe.QueryUserSubscribeNodeListHandler(serverCtx)) + + // Get subscribe group list + publicSubscribeGroupRouter.GET("/group/list", publicSubscribe.QuerySubscribeGroupListHandler(serverCtx)) } publicTicketGroupRouter := router.Group("/v1/public/ticket") @@ -957,11 +972,11 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Get Agent Downloads publicUserGroupRouter.GET("/agent_downloads", publicUser.GetAgentDownloadsHandler(serverCtx)) - publicUserGroupRouter.GET("/agent/downloads", publicUser.GetAgentDownloadsHandler(serverCtx)) + publicUserGroupRouter.GET("/agent/downloads", publicUser.GetAgentDownloadsHandler(serverCtx)) // alias: backward-compat // Get Agent Realtime publicUserGroupRouter.GET("/agent_realtime", publicUser.GetAgentRealtimeHandler(serverCtx)) - publicUserGroupRouter.GET("/agent/realtime", publicUser.GetAgentRealtimeHandler(serverCtx)) + publicUserGroupRouter.GET("/agent/realtime", publicUser.GetAgentRealtimeHandler(serverCtx)) // alias: backward-compat // Query User Balance Log publicUserGroupRouter.GET("/balance_log", publicUser.QueryUserBalanceLogHandler(serverCtx)) @@ -1010,11 +1025,11 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Get Invite Sales publicUserGroupRouter.GET("/invite_sales", publicUser.GetInviteSalesHandler(serverCtx)) - publicUserGroupRouter.GET("/invite/sales", publicUser.GetInviteSalesHandler(serverCtx)) + publicUserGroupRouter.GET("/invite/sales", publicUser.GetInviteSalesHandler(serverCtx)) // alias: backward-compat // Get User Invite Stats publicUserGroupRouter.GET("/invite_stats", publicUser.GetUserInviteStatsHandler(serverCtx)) - publicUserGroupRouter.GET("/invite/stats", publicUser.GetUserInviteStatsHandler(serverCtx)) + publicUserGroupRouter.GET("/invite/stats", publicUser.GetUserInviteStatsHandler(serverCtx)) // alias: backward-compat // Get Login Log publicUserGroupRouter.GET("/login_log", publicUser.GetLoginLogHandler(serverCtx)) diff --git a/internal/model/subscribe/model.go b/internal/model/subscribe/model.go index 06c0c6d..9764a27 100644 --- a/internal/model/subscribe/model.go +++ b/internal/model/subscribe/model.go @@ -2,6 +2,7 @@ package subscribe import ( "context" + "fmt" "strings" "github.com/perfect-panel/server/pkg/tool" @@ -113,7 +114,9 @@ func (m *customSubscribeModel) FilterList(ctx context.Context, params *FilterPar } if params.NodeGroupId != nil { // Filter by node_group_ids using JSON_CONTAINS - query = query.Where("JSON_CONTAINS(node_group_ids, ?)", *params.NodeGroupId) + // JSON_CONTAINS requires a JSON string, not a bare integer + jsonVal := fmt.Sprintf("%d", *params.NodeGroupId) + query = query.Where("(node_group_ids IS NOT NULL AND JSON_CONTAINS(node_group_ids, ?))", jsonVal) } if lang != "" { query = query.Where("language = ?", lang) @@ -205,8 +208,10 @@ func (m *customSubscribeModel) FilterListByNodeGroups(ctx context.Context, param // Condition 2: JSON_CONTAINS(node_group_ids, id) for each id for _, id := range params.NodeGroupIds { - conditions = append(conditions, "JSON_CONTAINS(node_group_ids, ?)") - args = append(args, id) + // JSON_CONTAINS requires a JSON string value, not a bare integer + jsonVal := fmt.Sprintf("%d", id) + conditions = append(conditions, "node_group_ids IS NOT NULL AND JSON_CONTAINS(node_group_ids, ?)") + args = append(args, jsonVal) } // Combine with OR: (node_group_id IN (...) OR JSON_CONTAINS(node_group_ids, id1) OR ...) diff --git a/internal/types/compat_types.go b/internal/types/compat_types.go index dd5b5d4..fc6b4e9 100644 --- a/internal/types/compat_types.go +++ b/internal/types/compat_types.go @@ -1,5 +1,13 @@ package types +// compat_types.go — 手动补充的类型,已同步到 .api 定义 +// 下次用 goctl 重新生成 types.go 后,以下类型会自动包含在 types.go 中,届时可以删除本文件中对应的定义: +// - ContactRequest (已加入 common.api) +// - GetDownloadLinkRequest / GetDownloadLinkResponse (已加入 common.api) +// - EmailLoginRequest (已更新 auth.api, 加入 form tag) +// - ReportLogMessageRequest / ReportLogMessageResponse (已加入 common.api, 路由 POST /v1/common/log/report) +// - LegacyCheckVerificationCodeRequest / LegacyCheckVerificationCodeResponse (已加入 common.api, 路由 POST /v1/common/check_code) + type ContactRequest struct { Name string `json:"name" validate:"required,max=100"` Email string `json:"email" validate:"required,email"`