feat: Multi-DB Driver

This commit is contained in:
tbphp
2025-07-17 18:15:46 +08:00
parent 068e63137e
commit 9575608fa7
13 changed files with 253 additions and 155 deletions

View File

@@ -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

1
.gitignore vendored
View File

@@ -213,3 +213,4 @@ charts/*.tgz
run-local.sh
start-local.sh
dev-setup.sh
data/

View File

@@ -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 | ❌ | 日志写入数据库周期(分钟) |
#### 请求设置

View File

@@ -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

View File

@@ -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

38
go.mod
View File

@@ -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
)

99
go.sum
View File

@@ -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=

View File

@@ -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"),
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"`

View File

@@ -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{

View File

@@ -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"`