From 7243079e9bd448c41bb7e7031a00f209912b1cb0 Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:17:22 +0800 Subject: [PATCH 1/7] feat: Add ClickHouse --- config.example.yaml | 15 ++++- internal/apps/project/clickhouses.go | 79 +++++++++++++++++++++++++ internal/apps/project/middlewares.go | 6 +- internal/apps/project/models.go | 21 ++++--- internal/config/model.go | 14 +++++ internal/db/clickhouse.go | 76 ++++++++++++++++++++++++ support-files/sql/create_clickhouse.sql | 19 ++++++ 7 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 internal/apps/project/clickhouses.go create mode 100644 internal/db/clickhouse.go create mode 100644 support-files/sql/create_clickhouse.sql diff --git a/config.example.yaml b/config.example.yaml index e36d44ca..715eff20 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -40,6 +40,19 @@ database: conn_max_lifetime: 3600 log_level: "info" # error, warn, info, debug, silent +# clickhouse +clickhouse: + enabled: false + hosts: + - "127.0.0.1:9000" + username: "default" + password: "" + database: "linux_do_cdk" + max_idle_conn: 10 + max_open_conn: 100 + conn_max_lifetime: 3600 + dial_timeout: 5 + # Redis配置 redis: host: "127.0.0.1" @@ -72,4 +85,4 @@ schedule: # Worker worker: - concurrency: 50 \ No newline at end of file + concurrency: 50 diff --git a/internal/apps/project/clickhouses.go b/internal/apps/project/clickhouses.go new file mode 100644 index 00000000..5a0e7bbd --- /dev/null +++ b/internal/apps/project/clickhouses.go @@ -0,0 +1,79 @@ +/* + * MIT License + * + * Copyright (c) 2025 linux.do + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package project + +import ( + "encoding/json" + "github.com/linux-do/cdk/internal/db" + "github.com/linux-do/cdk/internal/otel_trace" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "time" + + "github.com/gin-gonic/gin" + "github.com/linux-do/cdk/internal/config" +) + +func recordErrProjectReceive(c *gin.Context, reqTime time.Time, userId uint64, userName string, + projectId string, projectStartTime, projectEndTime time.Time, errorMsg string) { + if !config.Config.ClickHouse.Enabled { + return + } + // init trace + ctx, span := otel_trace.Start(c.Request.Context(), "ClickHouse") + defer span.End() + + clientInfo := map[string]interface{}{ + "ip": c.ClientIP(), + "user_agent": c.Request.UserAgent(), + "referer": c.Request.Referer(), + "host": c.Request.Host, + "method": c.Request.Method, + "path": c.Request.URL.Path, + "timestamp": time.Now().Format(time.RFC3339), + } + + clientInfoJSON, _ := json.Marshal(clientInfo) + traceID := trace.SpanFromContext(c.Request.Context()).SpanContext().TraceID().String() + + if errExec := db.ChConn.AsyncInsert(ctx, ` + INSERT INTO err_receive_logs ( + timestamp, trace_id, user_id, user_name, project_id, project_start_time, project_end_time, + error_message, client_info + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + true, + reqTime, + traceID, + userId, + userName, + projectId, + projectStartTime, + projectEndTime, + errorMsg, + string(clientInfoJSON), + ); errExec != nil { + span.SetStatus(codes.Error, errExec.Error()) + } +} diff --git a/internal/apps/project/middlewares.go b/internal/apps/project/middlewares.go index 8a4f53a7..680329ef 100644 --- a/internal/apps/project/middlewares.go +++ b/internal/apps/project/middlewares.go @@ -29,6 +29,7 @@ import ( "github.com/linux-do/cdk/internal/apps/oauth" "github.com/linux-do/cdk/internal/db" "net/http" + "time" ) func ProjectCreatorPermMiddleware() gin.HandlerFunc { @@ -59,15 +60,18 @@ func ReceiveProjectMiddleware() gin.HandlerFunc { c.AbortWithStatusJSON(http.StatusNotFound, ProjectResponse{ErrorMsg: err.Error()}) return } + now := time.Now() // load project projectID := c.Param("id") project := &Project{} if err := project.Exact(db.DB(ctx), projectID, true); err != nil { + recordErrProjectReceive(c, now, user.ID, user.Username, projectID, time.Time{}, time.Time{}, NotFound) c.AbortWithStatusJSON(http.StatusNotFound, ProjectResponse{ErrorMsg: err.Error()}) return } // check receivable - if err := project.IsReceivable(ctx, user, c.ClientIP()); err != nil { + if err := project.IsReceivable(ctx, now, user, c.ClientIP()); err != nil { + recordErrProjectReceive(c, now, user.ID, user.Username, project.ID, project.StartTime, project.EndTime, err.Error()) c.AbortWithStatusJSON(http.StatusForbidden, ProjectResponse{ErrorMsg: err.Error()}) return } diff --git a/internal/apps/project/models.go b/internal/apps/project/models.go index 16d4638e..edb63394 100644 --- a/internal/apps/project/models.go +++ b/internal/apps/project/models.go @@ -134,7 +134,7 @@ func (p *Project) CreateItemsWithFilter(ctx context.Context, tx *gorm.DB, items if len(items) <= 0 { return nil } - + filteredItems := items if enableFilter { // Get existing items for this project @@ -144,13 +144,13 @@ func (p *Project) CreateItemsWithFilter(ctx context.Context, tx *gorm.DB, items Pluck("content", &existingItems).Error; err != nil { return err } - + // Create a set of existing items for O(1) lookup existingSet := make(map[string]bool) for _, item := range existingItems { existingSet[item] = true } - + // Filter out duplicates filteredItems = make([]string, 0, len(items)) for _, item := range items { @@ -159,7 +159,7 @@ func (p *Project) CreateItemsWithFilter(ctx context.Context, tx *gorm.DB, items } } } - + // Use the original CreateItems method with filtered items return p.CreateItems(ctx, tx, filteredItems) } @@ -169,11 +169,11 @@ func (p *Project) GetFilteredItemsCount(ctx context.Context, tx *gorm.DB, items if len(items) <= 0 { return 0, nil } - + if !enableFilter { return int64(len(items)), nil } - + // Get existing items for this project var existingItems []string if err := tx.Model(&ProjectItem{}). @@ -181,13 +181,13 @@ func (p *Project) GetFilteredItemsCount(ctx context.Context, tx *gorm.DB, items Pluck("content", &existingItems).Error; err != nil { return 0, err } - + // Create a set of existing items for O(1) lookup existingSet := make(map[string]bool) for _, item := range existingItems { existingSet[item] = true } - + // Count unique items uniqueCount := int64(0) for _, item := range items { @@ -195,7 +195,7 @@ func (p *Project) GetFilteredItemsCount(ctx context.Context, tx *gorm.DB, items uniqueCount++ } } - + return uniqueCount, nil } @@ -236,9 +236,8 @@ func (p *Project) HasStock(ctx context.Context) (bool, error) { return stock > 0, nil } -func (p *Project) IsReceivable(ctx context.Context, user *oauth.User, ip string) error { +func (p *Project) IsReceivable(ctx context.Context, now time.Time, user *oauth.User, ip string) error { // check time - now := time.Now() if now.Before(p.StartTime) { return errors.New(TimeTooEarly) } else if p.EndTime.Before(now) { diff --git a/internal/config/model.go b/internal/config/model.go index 07a4e6f8..08a1a6cf 100644 --- a/internal/config/model.go +++ b/internal/config/model.go @@ -33,6 +33,7 @@ type configModel struct { Log logConfig `mapstructure:"log"` Schedule scheduleConfig `mapstructure:"schedule"` Worker workerConfig `mapstructure:"worker"` + ClickHouse clickHouseConfig `mapstructure:"clickhouse"` } // appConfig 应用基本配置 @@ -78,6 +79,19 @@ type databaseConfig struct { LogLevel string `mapstructure:"log_level"` } +// clickhouse 配置 +type clickHouseConfig struct { + Enabled bool `mapstructure:"enabled"` + Hosts []string `mapstructure:"hosts"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + Database string `mapstructure:"database"` + MaxIdleConn int `mapstructure:"max_idle_conn"` + MaxOpenConn int `mapstructure:"max_open_conn"` + ConnMaxLifetime int `mapstructure:"conn_max_lifetime"` + DialTimeout int `mapstructure:"dial_timeout"` +} + // redisConfig Redis配置 type redisConfig struct { Host string `mapstructure:"host"` diff --git a/internal/db/clickhouse.go b/internal/db/clickhouse.go new file mode 100644 index 00000000..3ea757a4 --- /dev/null +++ b/internal/db/clickhouse.go @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2025 linux.do + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package db + +import ( + "context" + "log" + "time" + + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" + "github.com/linux-do/cdk/internal/config" +) + +var ( + ChConn driver.Conn +) + +func init() { + if !config.Config.ClickHouse.Enabled { + return + } + + cfg := config.Config.ClickHouse + var err error + + // 配置 ClickHouse 连接 + ChConn, err = clickhouse.Open(&clickhouse.Options{ + Addr: cfg.Hosts, + Auth: clickhouse.Auth{ + Database: cfg.Database, + Username: cfg.Username, + Password: cfg.Password, + }, + Settings: clickhouse.Settings{ + "max_execution_time": 60, + }, + DialTimeout: time.Duration(cfg.DialTimeout) * time.Second, + MaxOpenConns: cfg.MaxOpenConn, + MaxIdleConns: cfg.MaxIdleConn, + ConnMaxLifetime: time.Duration(cfg.ConnMaxLifetime) * time.Second, + }) + + if err != nil { + log.Fatalf("[ClickHouse] init connection failed: %v\n", err) + } + + // 测试连接 + if err = ChConn.Ping(context.Background()); err != nil { + log.Fatalf("[ClickHouse] ping failed: %v\n", err) + } + + log.Println("[ClickHouse] connection established successfully") +} diff --git a/support-files/sql/create_clickhouse.sql b/support-files/sql/create_clickhouse.sql new file mode 100644 index 00000000..685dff33 --- /dev/null +++ b/support-files/sql/create_clickhouse.sql @@ -0,0 +1,19 @@ +CREATE DATABASE IF NOT EXISTS linux_do_cdk; + +USE linux_do_cdk; + +CREATE TABLE IF NOT EXISTS err_receive_logs +( + timestamp DateTime, + trace_id String, + user_id UInt64, + user_name String, + project_id String, + project_start_time DateTime, + project_end_time DateTime, + error_message String DEFAULT '', + client_info String DEFAULT '' +) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (timestamp, user_id, project_id) + SETTINGS index_granularity = 8192; From 26a0954021f660cf77e1fab7f1231eb578181fcd Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:15:35 +0800 Subject: [PATCH 2/7] fix: Rename timestamp to request_time in err_receive_logs and add user_id and project_id indexes --- internal/apps/project/clickhouses.go | 2 +- internal/apps/project/err.go | 1 + internal/apps/project/models.go | 2 +- support-files/sql/create_clickhouse.sql | 9 ++++++--- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/apps/project/clickhouses.go b/internal/apps/project/clickhouses.go index 5a0e7bbd..38988157 100644 --- a/internal/apps/project/clickhouses.go +++ b/internal/apps/project/clickhouses.go @@ -60,7 +60,7 @@ func recordErrProjectReceive(c *gin.Context, reqTime time.Time, userId uint64, u if errExec := db.ChConn.AsyncInsert(ctx, ` INSERT INTO err_receive_logs ( - timestamp, trace_id, user_id, user_name, project_id, project_start_time, project_end_time, + request_time, trace_id, user_id, user_name, project_id, project_start_time, project_end_time, error_message, client_info ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, true, diff --git a/internal/apps/project/err.go b/internal/apps/project/err.go index 5ff0f280..280a9dbb 100644 --- a/internal/apps/project/err.go +++ b/internal/apps/project/err.go @@ -31,6 +31,7 @@ const ( TimeTooLate = "已经结束" TrustLevelNotMatch = "需要信任等级 %d" UnknownError = "未知异常" + ScoreNotEnough = "分数未达标" SameIPReceived = "已有相同IP领取" NoStock = "无库存" NotFound = "项目不存在" diff --git a/internal/apps/project/models.go b/internal/apps/project/models.go index edb63394..0015ca9d 100644 --- a/internal/apps/project/models.go +++ b/internal/apps/project/models.go @@ -249,7 +249,7 @@ func (p *Project) IsReceivable(ctx context.Context, now time.Time, user *oauth.U } // check risk level if user.RiskLevel() > p.RiskLevel { - return errors.New(UnknownError) + return errors.New(ScoreNotEnough) } // check same ip if sameIPReceived, err := p.CheckSameIPReceived(ctx, ip); err != nil { diff --git a/support-files/sql/create_clickhouse.sql b/support-files/sql/create_clickhouse.sql index 685dff33..0ff648c7 100644 --- a/support-files/sql/create_clickhouse.sql +++ b/support-files/sql/create_clickhouse.sql @@ -4,7 +4,7 @@ USE linux_do_cdk; CREATE TABLE IF NOT EXISTS err_receive_logs ( - timestamp DateTime, + request_time DateTime, trace_id String, user_id UInt64, user_name String, @@ -14,6 +14,9 @@ CREATE TABLE IF NOT EXISTS err_receive_logs error_message String DEFAULT '', client_info String DEFAULT '' ) ENGINE = MergeTree() - PARTITION BY toYYYYMM(timestamp) - ORDER BY (timestamp, user_id, project_id) + PARTITION BY toYYYYMM(request_time) + ORDER BY (request_time, user_id, project_id) SETTINGS index_granularity = 8192; +ALTER TABLE err_receive_logs + ADD INDEX idx_user_id (user_id) TYPE bloom_filter(0.01) GRANULARITY 1, + ADD INDEX idx_project_id (project_id) TYPE bloom_filter(0.01) GRANULARITY 1; From 853ee8a91ef2000e95c5ffb8cc2eef9dd4f5590b Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:09:13 +0800 Subject: [PATCH 3/7] fix: Replace timestamp with origin in client info logging --- internal/apps/project/clickhouses.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/apps/project/clickhouses.go b/internal/apps/project/clickhouses.go index 38988157..ed0663d4 100644 --- a/internal/apps/project/clickhouses.go +++ b/internal/apps/project/clickhouses.go @@ -49,10 +49,7 @@ func recordErrProjectReceive(c *gin.Context, reqTime time.Time, userId uint64, u "ip": c.ClientIP(), "user_agent": c.Request.UserAgent(), "referer": c.Request.Referer(), - "host": c.Request.Host, - "method": c.Request.Method, - "path": c.Request.URL.Path, - "timestamp": time.Now().Format(time.RFC3339), + "origin": c.Request.Header.Get("Origin"), } clientInfoJSON, _ := json.Marshal(clientInfo) From 98c44229b1b5213b137015657e0cf26b22aa6b42 Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:32:35 +0800 Subject: [PATCH 4/7] fix(middleware): improve request validation and add missing headers --- frontend/middleware.ts | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/frontend/middleware.ts b/frontend/middleware.ts index d5227d8a..64a8e251 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -19,6 +19,7 @@ interface ProjectData { interface ReceiveRequestBody { captcha_token?: string; + [key: string]: string | number | boolean | null | undefined; } @@ -93,7 +94,10 @@ async function verifyCaptcha(token: string, remoteip?: string): Promise<{ succes /** * 验证项目时间有效性 */ -async function validateProjectTime(projectId: string, request: NextRequest): Promise<{ valid: boolean; error?: string }> { +async function validateProjectTime(projectId: string, request: NextRequest): Promise<{ + valid: boolean; + error?: string +}> { try { const projectCheckUrl = `${BACKEND_BASE_URL}/api/v1/projects/${projectId}`; @@ -163,8 +167,8 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro // 验证必要参数 if (!body.captcha_token) { return NextResponse.json( - {error_msg: '缺少必要的验证信息'}, - {status: 400}, + {error_msg: '缺少必要的验证信息'}, + {status: 400}, ); } @@ -172,25 +176,11 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro const captchaResult = await verifyCaptcha(body.captcha_token, clientIP); if (!captchaResult.success) { return NextResponse.json( - {error_msg: captchaResult.error || '人机验证失败,请重新验证'}, - {status: 400}, + {error_msg: captchaResult.error || '人机验证失败,请重新验证'}, + {status: 400}, ); } - // 提取项目ID并验证时间 - const pathParts = pathname.split('/'); - const projectId = pathParts[4]; // /api/v1/projects/{id}/receive - - if (projectId) { - const timeValidation = await validateProjectTime(projectId, request); - if (!timeValidation.valid) { - return NextResponse.json( - {error_msg: timeValidation.error}, - {status: 400}, - ); - } - } - // 准备转发到后端的请求 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars const {captcha_token: _captchaToken, ...backendBody} = body; @@ -204,6 +194,8 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro 'X-Original-Host': request.headers.get('host') || '', 'Cookie': request.headers.get('cookie') || '', 'User-Agent': 'CDK-Frontend-Middleware', + 'Referer': request.headers.get('referer') || '', + 'Origin': request.headers.get('origin') || '', }, body: Object.keys(backendBody).length > 0 ? JSON.stringify(backendBody) : undefined, credentials: 'include', @@ -219,8 +211,8 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro }); } catch (error) { return NextResponse.json( - {error_msg: formatErrorMessage(error, '服务器内部错误,请稍后重试')}, - {status: 500}, + {error_msg: formatErrorMessage(error, '服务器内部错误,请稍后重试')}, + {status: 500}, ); } } From edd6c4be238c509bc2dc3615f7d1633f87483fb7 Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:37:35 +0800 Subject: [PATCH 5/7] refactor(middleware): remove unused project time validation function --- frontend/middleware.ts | 63 ------------------------------------------ 1 file changed, 63 deletions(-) diff --git a/frontend/middleware.ts b/frontend/middleware.ts index 64a8e251..8dffeb0e 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -91,69 +91,6 @@ async function verifyCaptcha(token: string, remoteip?: string): Promise<{ succes } } -/** - * 验证项目时间有效性 - */ -async function validateProjectTime(projectId: string, request: NextRequest): Promise<{ - valid: boolean; - error?: string -}> { - try { - const projectCheckUrl = `${BACKEND_BASE_URL}/api/v1/projects/${projectId}`; - - // 使用 AbortController 实现超时 - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超时 - - const projectResponse = await fetch(projectCheckUrl, { - headers: { - 'Cookie': request.headers.get('cookie') || '', - 'User-Agent': 'CDK-Frontend-Middleware', - }, - credentials: 'include', - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!projectResponse.ok) { - if (projectResponse.status === 404) { - return {valid: false, error: '项目不存在或已被删除'}; - } - if (projectResponse.status === 403) { - return {valid: false, error: '无权访问此项目'}; - } - return {valid: false, error: `获取项目信息失败 (${projectResponse.status}),请稍后重试`}; - } - - const projectData: ProjectData = await projectResponse.json(); - - if (!projectData.data) { - return {valid: false, error: '项目不存在'}; - } - - const now = new Date(); - const startTime = new Date(projectData.data.start_time); - const endTime = new Date(projectData.data.end_time); - - if (now < startTime) { - return {valid: false, error: '项目尚未开始'}; - } - - if (now > endTime) { - return {valid: false, error: '项目已结束'}; - } - - return {valid: true}; - } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { - return {valid: false, error: '验证超时,请稍后重试'}; - } - - return {valid: false, error: formatErrorMessage(error, '服务器暂时无法验证项目信息,请稍后重试')}; - } -} - /** * 处理领取请求 */ From d0b52aa474d881f5cc4fe9428b4f418b59004bb1 Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:46:37 +0800 Subject: [PATCH 6/7] fix(middleware): Expected indentation of 10 spaces but found 8. 'ProjectData' is defined but never used. --- frontend/middleware.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/frontend/middleware.ts b/frontend/middleware.ts index 8dffeb0e..ce819759 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -10,13 +10,6 @@ interface HCaptchaResponse { 'error-codes'?: string[]; } -interface ProjectData { - data?: { - start_time: string; - end_time: string; - }; -} - interface ReceiveRequestBody { captcha_token?: string; @@ -104,8 +97,8 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro // 验证必要参数 if (!body.captcha_token) { return NextResponse.json( - {error_msg: '缺少必要的验证信息'}, - {status: 400}, + {error_msg: '缺少必要的验证信息'}, + {status: 400}, ); } @@ -113,8 +106,8 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro const captchaResult = await verifyCaptcha(body.captcha_token, clientIP); if (!captchaResult.success) { return NextResponse.json( - {error_msg: captchaResult.error || '人机验证失败,请重新验证'}, - {status: 400}, + {error_msg: captchaResult.error || '人机验证失败,请重新验证'}, + {status: 400}, ); } @@ -148,8 +141,8 @@ async function handleReceiveRequest(request: NextRequest, pathname: string): Pro }); } catch (error) { return NextResponse.json( - {error_msg: formatErrorMessage(error, '服务器内部错误,请稍后重试')}, - {status: 500}, + {error_msg: formatErrorMessage(error, '服务器内部错误,请稍后重试')}, + {status: 500}, ); } } From cb7f7dd06a174e81a6f7178f164891d37083c8f7 Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:01:30 +0800 Subject: [PATCH 7/7] feat(config): enable database and redis configurations --- config.example.yaml | 2 ++ go.mod | 2 +- internal/config/model.go | 2 ++ internal/db/mysql.go | 5 +++++ internal/db/redis.go | 5 +++++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/config.example.yaml b/config.example.yaml index 715eff20..8ccdacd9 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -30,6 +30,7 @@ oauth2: # DB database: + enabled: true host: "127.0.0.1" port: 3306 username: "root" @@ -55,6 +56,7 @@ clickhouse: # Redis配置 redis: + enabled: true host: "127.0.0.1" port: 6379 username: "" diff --git a/go.mod b/go.mod index eec87a23..03ac19d2 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/linux-do/cdk go 1.24 require ( + github.com/ClickHouse/clickhouse-go/v2 v2.23.2 github.com/gin-contrib/sessions v1.0.4 github.com/gin-gonic/gin v1.10.1 github.com/google/uuid v1.6.0 @@ -30,7 +31,6 @@ require ( require ( github.com/ClickHouse/ch-go v0.61.5 // indirect - github.com/ClickHouse/clickhouse-go/v2 v2.23.2 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/boj/redistore v1.4.1 // indirect diff --git a/internal/config/model.go b/internal/config/model.go index 08a1a6cf..3c77679c 100644 --- a/internal/config/model.go +++ b/internal/config/model.go @@ -68,6 +68,7 @@ type OAuth2Config struct { // databaseConfig 数据库配置 type databaseConfig struct { + Enabled bool `mapstructure:"enabled"` Host string `mapstructure:"host"` Port int `mapstructure:"port"` Username string `mapstructure:"username"` @@ -94,6 +95,7 @@ type clickHouseConfig struct { // redisConfig Redis配置 type redisConfig struct { + Enabled bool `mapstructure:"enabled"` Host string `mapstructure:"host"` Port int `mapstructure:"port"` Username string `mapstructure:"username"` diff --git a/internal/db/mysql.go b/internal/db/mysql.go index 005b150b..ee97d4e5 100644 --- a/internal/db/mysql.go +++ b/internal/db/mysql.go @@ -40,6 +40,11 @@ var ( ) func init() { + if !config.Config.Database.Enabled { + log.Println("[MySQL] is disabled, skipping MySQL initialization") + return + } + var err error dsn := fmt.Sprintf( diff --git a/internal/db/redis.go b/internal/db/redis.go index ec169c3c..4603445b 100644 --- a/internal/db/redis.go +++ b/internal/db/redis.go @@ -41,6 +41,11 @@ var ( func init() { redisConfig := config.Config.Redis + if !redisConfig.Enabled { + log.Println("[Redis] is disabled, skipping Redis initialization") + return + } + addr := fmt.Sprintf("%s:%d", redisConfig.Host, redisConfig.Port) Redis = redis.NewClient(