From 9575608fa75806f78279d6154239fdb411e0ebe5 Mon Sep 17 00:00:00 2001 From: tbphp Date: Thu, 17 Jul 2025 18:15:46 +0800 Subject: [PATCH] feat: Multi-DB Driver --- .env.example | 17 ++-- .gitignore | 1 + README.md | 40 +++++----- README_EN.md | 40 +++++----- docker-compose.yml | 94 +++++++++++++--------- go.mod | 38 ++++++--- go.sum | 99 +++++++++++++++--------- internal/config/manager.go | 4 +- internal/db/database.go | 41 ++++++++-- internal/errors/errors.go | 24 ++++-- internal/models/types.go | 4 +- internal/services/request_log_service.go | 4 +- internal/types/types.go | 2 +- 13 files changed, 253 insertions(+), 155 deletions(-) diff --git a/.env.example b/.env.example index bc704ba..cacb0cb 100644 --- a/.env.example +++ b/.env.example @@ -17,13 +17,14 @@ TZ=Asia/Shanghai # 认证配置 是必需的,用于保护管理 API 和 UI 界面 AUTH_KEY=sk-123456 -# 数据库配置 -# 示例 DSN: user:password@tcp(localhost:3306)/gpt_load?charset=utf8mb4&parseTime=True&loc=Local -DATABASE_DSN=root:123456@tcp(mysql:3306)/gpt_load?charset=utf8mb4&parseTime=True&loc=Local +# 数据库配置 默认不填写,使用./data/gpt-load.db的SQLite +# MySQL 示例: +# DATABASE_DSN="root:123456@tcp(mysql:3306)/gpt-load?charset=utf8mb4&parseTime=True&loc=Local" +# PostgreSQL 示例: +# DATABASE_DSN="postgres://postgres:123456@postgres:5432/gpt-load?sslmode=disable" -# Redis配置 -# 示例 DSN: redis://:password@localhost:6379/0 -REDIS_DSN=redis://redis:6379/0 +# Redis配置 默认不填写,使用内存存储 +# REDIS_DSN=redis://redis:6379/0 # 并发数量 MAX_CONCURRENT_REQUESTS=100 @@ -38,5 +39,5 @@ ALLOW_CREDENTIALS=false # 日志配置 LOG_LEVEL=info LOG_FORMAT=text -LOG_ENABLE_FILE=false -LOG_FILE_PATH=logs/app.log +LOG_ENABLE_FILE=true +LOG_FILE_PATH=./data/logs/app.log diff --git a/.gitignore b/.gitignore index ed6495d..a970fab 100644 --- a/.gitignore +++ b/.gitignore @@ -213,3 +213,4 @@ charts/*.tgz run-local.sh start-local.sh dev-setup.sh +data/ diff --git a/README.md b/README.md index 211493d..b079a4b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ 中文文档 | [English](README_EN.md) [![Release](https://img.shields.io/github/v/release/tbphp/gpt-load)](https://github.com/tbphp/gpt-load/releases) -[![Build Docker Image](https://github.com/tbphp/gpt-load/actions/workflows/docker-build.yml/badge.svg)](https://github.com/tbphp/gpt-load/actions/workflows/docker-build.yml) ![Go Version](https://img.shields.io/badge/Go-1.23+-blue.svg) ![License](https://img.shields.io/badge/license-MIT-green.svg) @@ -38,7 +37,7 @@ GPT-Load 作为透明代理服务,完整保留各 AI 服务商的原生 API - Go 1.23+ (源码构建) - Docker (容器化部署) -- MySQL 8.2+ (数据库存储) +- MySQL, PostgreSQL, 或 SQLite (数据库存储) - Redis (缓存和分布式协调,可选) ### 方式一:使用 Docker Compose(推荐) @@ -53,13 +52,14 @@ mkdir -p gpt-load && cd gpt-load wget https://raw.githubusercontent.com/tbphp/gpt-load/refs/heads/main/docker-compose.yml wget -O .env https://raw.githubusercontent.com/tbphp/gpt-load/refs/heads/main/.env.example -# 编辑配置文件(根据需要修改服务端口和认证Key等) -# vim .env - -# 启动服务(包含 MySQL 和 Redis) +# 启动服务 docker compose up -d ``` +默认安装的是 SQLite 版本,适合轻量单机应用。 + +如需安装 MySQL, PostgreSQL 及 Redis,请在 `docker-compose.yml` 文件中取消所需服务的注释,并配置好对应的环境配置重启即可。 + **其他命令:** ```bash @@ -85,7 +85,7 @@ docker compose pull && docker compose down && docker compose up -d ### 方式二:源码构建 -源码构建需要本地已安装 MySQL 和 Redis(可选)。 +源码构建需要本地已安装数据库(SQLite、MySQL 或 PostgreSQL)和 Redis(可选)。 ```bash # 克隆并构建 @@ -112,7 +112,7 @@ make run ### 方式三:集群部署 -集群部署需要所有节点都连接同一个 MySQL 和 Redis,并且 Redis 是必须要求。建议使用统一的分布式 MySQL 和 Redis 集群。 +集群部署需要所有节点都连接同一个 MySQL(或者 PostgreSQL) 和 Redis,并且 Redis 是必须要求。建议使用统一的分布式 MySQL 和 Redis 集群。 **部署要求:** @@ -157,11 +157,11 @@ GPT-Load 采用双层配置架构: #### 认证与数据库配置 -| 配置项 | 环境变量 | 默认值 | 说明 | -| ---------- | -------------- | ----------- | ------------------------------------ | -| 认证密钥 | `AUTH_KEY` | `sk-123456` | 访问管理端以及请求代理的唯一认证密钥 | -| 数据库连接 | `DATABASE_DSN` | - | MySQL 数据库连接字符串 | -| Redis 连接 | `REDIS_DSN` | - | Redis 连接字符串,为空时使用内存存储 | +| 配置项 | 环境变量 | 默认值 | 说明 | +| ---------- | -------------- | ------------------ | ------------------------------------ | +| 认证密钥 | `AUTH_KEY` | `sk-123456` | 访问管理端以及请求代理的唯一认证密钥 | +| 数据库连接 | `DATABASE_DSN` | ./data/gpt-load.db | 数据库连接字符串 (DSN) 或文件路径 | +| Redis 连接 | `REDIS_DSN` | - | Redis 连接字符串,为空时使用内存存储 | #### 性能与跨域配置 @@ -176,12 +176,12 @@ GPT-Load 采用双层配置架构: #### 日志配置 -| 配置项 | 环境变量 | 默认值 | 说明 | -| ------------ | ----------------- | -------------- | ---------------------------------- | -| 日志级别 | `LOG_LEVEL` | `info` | 日志级别:debug, info, warn, error | -| 日志格式 | `LOG_FORMAT` | `text` | 日志格式:text, json | -| 启用文件日志 | `LOG_ENABLE_FILE` | false | 是否启用文件日志输出 | -| 日志文件路径 | `LOG_FILE_PATH` | `logs/app.log` | 日志文件存储路径 | +| 配置项 | 环境变量 | 默认值 | 说明 | +| ------------ | ----------------- | --------------------- | ---------------------------------- | +| 日志级别 | `LOG_LEVEL` | `info` | 日志级别:debug, info, warn, error | +| 日志格式 | `LOG_FORMAT` | `text` | 日志格式:text, json | +| 启用文件日志 | `LOG_ENABLE_FILE` | false | 是否启用文件日志输出 | +| 日志文件路径 | `LOG_FILE_PATH` | `./data/logs/app.log` | 日志文件存储路径 | ### 动态配置(热重载) @@ -195,7 +195,7 @@ GPT-Load 采用双层配置架构: | ------------ | ------------------------------------ | ----------------------- | ---------- | ---------------------------- | | 项目地址 | `app_url` | `http://localhost:3001` | ❌ | 项目基础 URL | | 日志保留天数 | `request_log_retention_days` | 7 | ❌ | 请求日志保留天数,0 为不清理 | -| 日志写入间隔 | `request_log_write_interval_minutes` | 5 | ❌ | 日志写入数据库周期(分钟) | +| 日志写入间隔 | `request_log_write_interval_minutes` | 1 | ❌ | 日志写入数据库周期(分钟) | #### 请求设置 diff --git a/README_EN.md b/README_EN.md index f01510f..3809a98 100644 --- a/README_EN.md +++ b/README_EN.md @@ -3,7 +3,6 @@ [中文文档](README.md) | English [![Release](https://img.shields.io/github/v/release/tbphp/gpt-load)](https://github.com/tbphp/gpt-load/releases) -[![Build Docker Image](https://github.com/tbphp/gpt-load/actions/workflows/docker-build.yml/badge.svg)](https://github.com/tbphp/gpt-load/actions/workflows/docker-build.yml) ![Go Version](https://img.shields.io/badge/Go-1.23+-blue.svg) ![License](https://img.shields.io/badge/license-MIT-green.svg) @@ -38,7 +37,7 @@ GPT-Load serves as a transparent proxy service, completely preserving the native - Go 1.23+ (for source builds) - Docker (for containerized deployment) -- MySQL 8.2+ (for database storage) +- MySQL, PostgreSQL, or SQLite (for database storage) - Redis (for caching and distributed coordination, optional) ### Method 1: Using Docker Compose (Recommended) @@ -53,13 +52,14 @@ mkdir -p gpt-load && cd gpt-load wget https://raw.githubusercontent.com/tbphp/gpt-load/refs/heads/main/docker-compose.yml wget -O .env https://raw.githubusercontent.com/tbphp/gpt-load/refs/heads/main/.env.example -# Edit configuration file (modify service port and authentication key as needed) -# vim .env - -# Start services (includes MySQL and Redis) +# Start services docker compose up -d ``` +The default installation uses the SQLite version, which is suitable for lightweight, single-instance applications. + +If you need to install MySQL, PostgreSQL, and Redis, please uncomment the required services in the `docker-compose.yml` file, configure the corresponding environment variables, and restart. + **Other Commands:** ```bash @@ -85,7 +85,7 @@ After deployment: ### Method 2: Source Build -Source build requires locally installed MySQL and Redis (optional). +Source build requires a locally installed database (SQLite, MySQL, or PostgreSQL) and Redis (optional). ```bash # Clone and build @@ -112,7 +112,7 @@ After deployment: ### Method 3: Cluster Deployment -Cluster deployment requires all nodes to connect to the same MySQL and Redis, with Redis being mandatory. It's recommended to use unified distributed MySQL and Redis clusters. +Cluster deployment requires all nodes to connect to the same MySQL (or PostgreSQL) and Redis, with Redis being mandatory. It's recommended to use unified distributed MySQL and Redis clusters. **Deployment Requirements:** @@ -157,11 +157,11 @@ GPT-Load adopts a dual-layer configuration architecture: #### Authentication & Database Configuration -| Setting | Environment Variable | Default | Description | -| ------------------- | -------------------- | ----------- | ------------------------------------------------------------------------------- | -| Authentication Key | `AUTH_KEY` | `sk-123456` | Unique authentication key for accessing management interface and proxy requests | -| Database Connection | `DATABASE_DSN` | - | MySQL database connection string | -| Redis Connection | `REDIS_DSN` | - | Redis connection string, uses memory storage when empty | +| Setting | Environment Variable | Default | Description | +| ------------------- | -------------------- | -------------------- | ------------------------------------------------------------------------------- | +| Authentication Key | `AUTH_KEY` | `sk-123456` | Unique authentication key for accessing management interface and proxy requests | +| Database Connection | `DATABASE_DSN` | `./data/gpt-load.db` | Database connection string (DSN) or file path | +| Redis Connection | `REDIS_DSN` | - | Redis connection string, uses memory storage when empty | #### Performance & CORS Configuration @@ -176,12 +176,12 @@ GPT-Load adopts a dual-layer configuration architecture: #### Logging Configuration -| Setting | Environment Variable | Default | Description | -| ------------------- | -------------------- | -------------- | ----------------------------------- | -| Log Level | `LOG_LEVEL` | `info` | Log level: debug, info, warn, error | -| Log Format | `LOG_FORMAT` | `text` | Log format: text, json | -| Enable File Logging | `LOG_ENABLE_FILE` | false | Whether to enable file log output | -| Log File Path | `LOG_FILE_PATH` | `logs/app.log` | Log file storage path | +| Setting | Environment Variable | Default | Description | +| ------------------- | -------------------- | --------------------- | ----------------------------------- | +| Log Level | `LOG_LEVEL` | `info` | Log level: debug, info, warn, error | +| Log Format | `LOG_FORMAT` | `text` | Log format: text, json | +| Enable File Logging | `LOG_ENABLE_FILE` | false | Whether to enable file log output | +| Log File Path | `LOG_FILE_PATH` | `./data/logs/app.log` | Log file storage path | ### Dynamic Configuration (Hot-Reload) @@ -195,7 +195,7 @@ Dynamic configuration is stored in the database and supports real-time modificat | ------------------ | ------------------------------------ | ----------------------- | -------------- | -------------------------------------------- | | Project URL | `app_url` | `http://localhost:3001` | ❌ | Project base URL | | Log Retention Days | `request_log_retention_days` | 7 | ❌ | Request log retention days, 0 for no cleanup | -| Log Write Interval | `request_log_write_interval_minutes` | 5 | ❌ | Log write to database cycle (minutes) | +| Log Write Interval | `request_log_write_interval_minutes` | 1 | ❌ | Log write to database cycle (minutes) | #### Request Settings diff --git a/docker-compose.yml b/docker-compose.yml index cd5c6d8..bd5e1dd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,49 +10,71 @@ services: env_file: - .env restart: always + volumes: + - ./data:/app/data stop_grace_period: ${SERVER_GRACEFUL_SHUTDOWN_TIMEOUT:-10}s - depends_on: - mysql: - condition: service_healthy - restart: true - redis: - condition: service_healthy - restart: true healthcheck: test: wget -q --spider -T 10 -O /dev/null http://localhost:${PORT:-3001}/health interval: 30s timeout: 10s retries: 3 start_period: 40s + # depends_on: + # mysql: + # condition: service_healthy + # restart: true + # postgres: + # condition: service_healthy + # restart: true + # redis: + # condition: service_healthy + # restart: true - mysql: - image: mysql:8.2 - container_name: gpt-load-mysql - restart: always - # ports: - # - "3306:3306" - environment: - MYSQL_ROOT_PASSWORD: 123456 - MYSQL_DATABASE: gpt_load - volumes: - - mysql_data:/var/lib/mysql - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 5s - timeout: 5s - retries: 10 + # 如果需要安装 MySQL、PostgreSQL 或 Redis,请取消以下注释并配置相应的环境变量。 + # 并且要在上方的depends_on中取消注释相应的依赖服务。 - redis: - image: redis:latest - container_name: gpt-load-redis - restart: always - # ports: - # - "6379:6379" - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 3s - retries: 3 + # mysql: + # image: mysql:8.2 + # container_name: gpt-load-mysql + # restart: always + # ports: + # - "3306:3306" + # environment: + # MYSQL_ROOT_PASSWORD: 123456 + # MYSQL_DATABASE: gpt-load + # volumes: + # - ./data/mysql:/var/lib/mysql + # healthcheck: + # test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + # interval: 5s + # timeout: 5s + # retries: 10 -volumes: - mysql_data: + # postgres: + # image: "postgres:16" + # container_name: gpt-load-postgres + # environment: + # POSTGRES_USER: postgres + # POSTGRES_PASSWORD: 123456 + # POSTGRES_DB: gpt-load + # ports: + # - "5432:5432" + # volumes: + # - ./data/postgres:/var/lib/postgresql/data + # healthcheck: + # test: ["CMD-SHELL", "pg_isready -U postgres -d gpt-load"] + # interval: 5s + # timeout: 5s + # retries: 10 + + # redis: + # image: redis:latest + # container_name: gpt-load-redis + # restart: always + # ports: + # - "6379:6379" + # healthcheck: + # test: ["CMD", "redis-cli", "ping"] + # interval: 5s + # timeout: 3s + # retries: 3 diff --git a/go.mod b/go.mod index dd5dc84..dad50f1 100644 --- a/go.mod +++ b/go.mod @@ -8,47 +8,61 @@ require ( github.com/gin-contrib/gzip v1.2.3 github.com/gin-contrib/static v1.1.5 github.com/gin-gonic/gin v1.10.1 + github.com/glebarez/sqlite v1.11.0 github.com/go-sql-driver/mysql v1.8.1 github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v5 v5.6.0 github.com/joho/godotenv v1.5.1 github.com/redis/go-redis/v9 v9.5.3 github.com/sirupsen/logrus v1.9.3 go.uber.org/dig v1.19.0 gorm.io/datatypes v1.2.1 gorm.io/driver/mysql v1.6.0 + gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.30.0 ) require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/bytedance/sonic v1.13.3 // indirect + github.com/bytedance/sonic v1.13.2 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/gabriel-vasile/mimetype v1.4.9 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.0.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.0 // indirect - golang.org/x/arch v0.18.0 // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.16.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect ) diff --git a/go.sum b/go.sum index 679cbd6..157ecbb 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= -github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= +github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= @@ -20,24 +20,30 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= -github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U= github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= +github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-contrib/static v1.1.5 h1:bAPqT4KTZN+4uDY1b90eSrD1t8iNzod7Jj8njwmnzz4= github.com/gin-contrib/static v1.1.5/go.mod h1:8JSEXwZHcQ0uCrLPcsvnAJ4g+ODxeupP8Zetl9fd8wM= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= @@ -46,19 +52,21 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= -github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -68,8 +76,8 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= -github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -79,8 +87,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -88,12 +96,15 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -101,33 +112,37 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= -github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc= -golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= +golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,12 +155,20 @@ gorm.io/datatypes v1.2.1 h1:r+g0bk4LPCW2v4+Ls7aeNgGme7JYdNDQ2VtvlNUfBh0= gorm.io/datatypes v1.2.1/go.mod h1:hYK6OTb/1x+m96PgoZZq10UXJ6RvEBb9kRDQ2yyhzGs= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= -gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/internal/config/manager.go b/internal/config/manager.go index 05af8e7..7f581e2 100644 --- a/internal/config/manager.go +++ b/internal/config/manager.go @@ -95,10 +95,10 @@ func (m *Manager) ReloadConfig() error { Level: utils.GetEnvOrDefault("LOG_LEVEL", "info"), Format: utils.GetEnvOrDefault("LOG_FORMAT", "text"), EnableFile: utils.ParseBoolean(os.Getenv("LOG_ENABLE_FILE"), false), - FilePath: utils.GetEnvOrDefault("LOG_FILE_PATH", "logs/app.log"), + FilePath: utils.GetEnvOrDefault("LOG_FILE_PATH", "./data/logs/app.log"), }, Database: types.DatabaseConfig{ - DSN: os.Getenv("DATABASE_DSN"), + DSN: utils.GetEnvOrDefault("DATABASE_DSN", "./data/gpt-load.db"), }, RedisDSN: os.Getenv("REDIS_DSN"), } diff --git a/internal/db/database.go b/internal/db/database.go index d92bef9..e0279c5 100644 --- a/internal/db/database.go +++ b/internal/db/database.go @@ -5,9 +5,13 @@ import ( "gpt-load/internal/types" "log" "os" + "path/filepath" + "strings" "time" + "github.com/glebarez/sqlite" "gorm.io/driver/mysql" + "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) @@ -16,7 +20,8 @@ var DB *gorm.DB func NewDB(configManager types.ConfigManager) (*gorm.DB, error) { dbConfig := configManager.GetDatabaseConfig() - if dbConfig.DSN == "" { + dsn := dbConfig.DSN + if dsn == "" { return nil, fmt.Errorf("DATABASE_DSN is not configured") } @@ -33,9 +38,32 @@ func NewDB(configManager types.ConfigManager) (*gorm.DB, error) { ) } + var dialector gorm.Dialector + if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { + dialector = postgres.New(postgres.Config{ + DSN: dsn, + PreferSimpleProtocol: true, + }) + } else if strings.Contains(dsn, "@tcp") { + if !strings.Contains(dsn, "parseTime") { + if strings.Contains(dsn, "?") { + dsn += "&parseTime=true" + } else { + dsn += "?parseTime=true" + } + } + dialector = mysql.Open(dsn) + } else { + if err := os.MkdirAll(filepath.Dir(dsn), 0755); err != nil { + return nil, fmt.Errorf("failed to create database directory: %w", err) + } + dialector = sqlite.Open(dsn + "?_pragma=journal_mode(WAL)&_busy_timeout=5000") + } + var err error - DB, err = gorm.Open(mysql.Open(dbConfig.DSN), &gorm.Config{ - Logger: newLogger, + DB, err = gorm.Open(dialector, &gorm.Config{ + Logger: newLogger, + PrepareStmt: true, }) if err != nil { return nil, fmt.Errorf("failed to connect to database: %w", err) @@ -45,10 +73,9 @@ func NewDB(configManager types.ConfigManager) (*gorm.DB, error) { if err != nil { return nil, fmt.Errorf("failed to get sql.DB: %w", err) } - - // Set connection pool parameters - sqlDB.SetMaxIdleConns(10) - sqlDB.SetMaxOpenConns(100) + // Set connection pool parameters for all drivers + sqlDB.SetMaxIdleConns(50) + sqlDB.SetMaxOpenConns(500) sqlDB.SetConnMaxLifetime(time.Hour) return DB, nil diff --git a/internal/errors/errors.go b/internal/errors/errors.go index bc932bd..f192cb6 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -3,8 +3,10 @@ package errors import ( "errors" "net/http" + "strings" "github.com/go-sql-driver/mysql" + "github.com/jackc/pgx/v5/pgconn" "gorm.io/gorm" ) @@ -62,20 +64,28 @@ func ParseDBError(err error) *APIError { return nil } - // Handle record not found error if errors.Is(err, gorm.ErrRecordNotFound) { return ErrResourceNotFound } - // Handle MySQL specific errors - var mysqlErr *mysql.MySQLError - if errors.As(err, &mysqlErr) { - switch mysqlErr.Number { - case 1062: // Duplicate entry for unique key + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + if pgErr.Code == "23505" { // unique_violation return ErrDuplicateResource } } - // Default to a generic database error + var mysqlErr *mysql.MySQLError + if errors.As(err, &mysqlErr) { + if mysqlErr.Number == 1062 { // Duplicate entry + return ErrDuplicateResource + } + } + + // Generic check for SQLite + if strings.Contains(strings.ToLower(err.Error()), "unique constraint failed") { + return ErrDuplicateResource + } + return ErrDatabase } diff --git a/internal/models/types.go b/internal/models/types.go index f619f37..071ba7e 100644 --- a/internal/models/types.go +++ b/internal/models/types.go @@ -74,7 +74,7 @@ type APIKey struct { // RequestLog 对应 request_logs 表 type RequestLog struct { ID string `gorm:"type:varchar(36);primaryKey" json:"id"` - Timestamp time.Time `gorm:"type:datetime(3);not null;index" json:"timestamp"` + Timestamp time.Time `gorm:"not null;index" json:"timestamp"` GroupID uint `gorm:"not null;index" json:"group_id"` KeyID uint `gorm:"not null;index" json:"key_id"` IsSuccess bool `gorm:"not null" json:"is_success"` @@ -122,7 +122,7 @@ type ChartData struct { // GroupHourlyStat 对应 group_hourly_stats 表,用于存储每个分组每小时的请求统计 type GroupHourlyStat struct { ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - Time time.Time `gorm:"type:datetime;not null;uniqueIndex:idx_group_time" json:"time"` // 整点时间 + Time time.Time `gorm:"not null;uniqueIndex:idx_group_time" json:"time"` // 整点时间 GroupID uint `gorm:"not null;uniqueIndex:idx_group_time" json:"group_id"` SuccessCount int64 `gorm:"not null;default:0" json:"success_count"` FailureCount int64 `gorm:"not null;default:0" json:"failure_count"` diff --git a/internal/services/request_log_service.go b/internal/services/request_log_service.go index dd232f0..1b77ef1 100644 --- a/internal/services/request_log_service.go +++ b/internal/services/request_log_service.go @@ -259,8 +259,8 @@ func (s *RequestLogService) writeLogsToDB(logs []*models.RequestLog) error { err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "time"}, {Name: "group_id"}}, DoUpdates: clause.Assignments(map[string]any{ - "success_count": gorm.Expr("success_count + ?", counts.Success), - "failure_count": gorm.Expr("failure_count + ?", counts.Failure), + "success_count": gorm.Expr("group_hourly_stats.success_count + ?", counts.Success), + "failure_count": gorm.Expr("group_hourly_stats.failure_count + ?", counts.Failure), "updated_at": time.Now(), }), }).Create(&models.GroupHourlyStat{ diff --git a/internal/types/types.go b/internal/types/types.go index dcab432..6526427 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -20,7 +20,7 @@ type SystemSettings struct { // 基础参数 AppUrl string `json:"app_url" default:"http://localhost:3001" name:"项目地址" category:"基础参数" desc:"项目的基础 URL,用于拼接分组终端节点地址。系统配置优先于环境变量 APP_URL。"` RequestLogRetentionDays int `json:"request_log_retention_days" default:"7" name:"日志保留时长(天)" category:"基础参数" desc:"请求日志在数据库中的保留天数,0为不清理日志。" validate:"min=0"` - RequestLogWriteIntervalMinutes int `json:"request_log_write_interval_minutes" default:"5" name:"日志延迟写入周期(分钟)" category:"基础参数" desc:"请求日志从缓存写入数据库的周期(分钟),0为实时写入数据。" validate:"min=0"` + RequestLogWriteIntervalMinutes int `json:"request_log_write_interval_minutes" default:"1" name:"日志延迟写入周期(分钟)" category:"基础参数" desc:"请求日志从缓存写入数据库的周期(分钟),0为实时写入数据。" validate:"min=0"` // 请求设置 RequestTimeout int `json:"request_timeout" default:"600" name:"请求超时(秒)" category:"请求设置" desc:"转发请求的完整生命周期超时(秒)等。" validate:"min=1"`