vault backup: 2026-04-22 10:10:19
This commit is contained in:
@@ -138,7 +138,7 @@ erDiagram
|
||||
string password "密码(hash)"
|
||||
datetime created_at "创建时间"
|
||||
datetime updated_at "更新时间"
|
||||
datetime deleted_at "删除时间(软删除)"
|
||||
tinyint is_deleted "软删除标记"
|
||||
}
|
||||
WORD {
|
||||
uuid id PK "单词记录ID"
|
||||
@@ -149,7 +149,7 @@ erDiagram
|
||||
string ai_provider "AI模型来源"
|
||||
datetime created_at "创建时间"
|
||||
datetime updated_at "更新时间"
|
||||
datetime deleted_at "删除时间(软删除)"
|
||||
tinyint is_deleted "软删除标记"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -160,7 +160,7 @@ erDiagram
|
||||
- 用户名设置唯一索引,防止重复注册
|
||||
- 密码字段存储 bcrypt 哈希值,严禁明文
|
||||
- 包含标准时间戳字段
|
||||
- 支持软删除(deleted_at)
|
||||
- 支持软删除(is_deleted)
|
||||
|
||||
2. **单词表 (words)**
|
||||
- 使用 UUID 作为主键
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
# Docker 网络数据库连接指南
|
||||
|
||||
## 问题描述
|
||||
|
||||
在后端服务连接运行在 Docker 网络中的数据库时,**不能使用 `localhost`** 作为连接地址。
|
||||
|
||||
## 原因分析
|
||||
|
||||
### 为什么使用 `localhost` 是错误的?
|
||||
|
||||
在 Docker 网络中,每个容器都有**独立的网络栈**:
|
||||
|
||||
- **`localhost` 在容器内部指向容器自身**(127.0.0.1 指向容器内部)
|
||||
- **不是指向同一网络中的其他容器**
|
||||
|
||||
当你的后端容器尝试连接 `localhost:3306` 时,它实际上是在寻找**自己的 3306 端口**,而不是数据库容器的端口。
|
||||
|
||||
#### 直观理解
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Docker 网络(我的网络) │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ Backend 容器 │ │ Database 容器 │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ localhost ───────┼──→ │ 自己的 8000 端口 │ │
|
||||
│ │ (127.0.0.1) │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ database ────────┼──→ │ 正确!指向 DB │ │
|
||||
│ │ (服务名) │ │ │ │
|
||||
│ └──────────────────┘ └──────────────────┘ │
|
||||
│ ↑ ↑ │
|
||||
│ └──────────┬───────────────┘ │
|
||||
│ │ │
|
||||
│ 只能通过服务名通信 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 实际会发生什么
|
||||
|
||||
```bash
|
||||
# 在 backend 容器内执行
|
||||
# 尝试连接 localhost:3306
|
||||
# 结果:Connection refused
|
||||
# 原因:backend 自己没有在 3306 端口监听服务
|
||||
```
|
||||
|
||||
#### 类比说明
|
||||
|
||||
这就好比:
|
||||
- 你和朋友住在同一个大楼(Docker 网络)
|
||||
- 你想找朋友
|
||||
- 你大喊"我在这里"(使用 `localhost`)
|
||||
- 但实际上你需要喊的是"小明"(使用服务的名字)
|
||||
- 因为 `"我在这里"` 指的是你自己,不是小明
|
||||
|
||||
### Docker 的网络隔离机制
|
||||
|
||||
Docker 容器默认使用以下网络模式:
|
||||
|
||||
1. **Bridge 网络模式**(默认):
|
||||
- 每个容器有独立的 IP 地址
|
||||
- 容器间通过服务名互相发现
|
||||
- `localhost` 仅指向容器自身
|
||||
|
||||
2. **容器间的 DNS 解析**:
|
||||
```
|
||||
# 在 backend 容器内
|
||||
$ ping database
|
||||
PING database (172.19.0.2) // 解析到数据库容器的 IP
|
||||
|
||||
$ ping localhost
|
||||
PING localhost (127.0.0.1) // 指向自己
|
||||
```
|
||||
|
||||
3. **端口监听位置**:
|
||||
```
|
||||
Database 容器内部:MySQL 监听 0.0.0.0:3306
|
||||
Database 容器 IP:172.19.0.2
|
||||
Backend 容器不能访问:127.0.0.1:3306(自己的端口)
|
||||
Backend 容器应该访问:172.19.0.2:3306(通过服务名解析)
|
||||
```
|
||||
|
||||
### 结论
|
||||
|
||||
**使用 `localhost` 连接数据库必然失败**,因为它:
|
||||
- 永远指向调用方容器自己
|
||||
- 无法跨容器通信
|
||||
- 违反了 Docker 的网络隔离设计
|
||||
|
||||
**正确做法:使用服务名或容器名**,这样 Docker 的内置 DNS 才能正确解析到目标容器。
|
||||
|
||||
## 正确做法
|
||||
|
||||
### 1. 使用服务名(Service Name)
|
||||
|
||||
在 `docker-compose.yml` 中定义的服务名即可用作主机名:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
backend:
|
||||
build: ./backend
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
```
|
||||
|
||||
**连接配置:**
|
||||
```python
|
||||
# 后端连接配置
|
||||
DATABASE_URL = "mysql://user:password@database:3306/dbname"
|
||||
# ^^^^^^^^ 使用服务名 "database"
|
||||
```
|
||||
|
||||
### 2. 使用容器名
|
||||
|
||||
在 docker-compose 中为容器指定名称:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
image: mysql:8.0
|
||||
container_name: my-mysql-db # 显式指定容器名
|
||||
```
|
||||
|
||||
**连接配置:**
|
||||
```python
|
||||
DATABASE_URL = "mysql://user:password@my-mysql-db:3306/dbname"
|
||||
```
|
||||
|
||||
### 3. 网络别名(Network Alias)
|
||||
|
||||
如果有多个服务需要连接,可以设置网络别名:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
image: mysql:8.0
|
||||
networks:
|
||||
appnet:
|
||||
aliases:
|
||||
- db
|
||||
- mysql
|
||||
|
||||
networks:
|
||||
appnet:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
**连接配置:**
|
||||
```python
|
||||
# 可以使用任一别名
|
||||
DATABASE_URL = "mysql://user:password@db:3306/dbname"
|
||||
# 或
|
||||
DATABASE_URL = "mysql://user:password@mysql:3306/dbname"
|
||||
```
|
||||
|
||||
## 常见错误
|
||||
|
||||
### ❌ 错误写法
|
||||
```python
|
||||
# 这会导致连接失败
|
||||
DATABASE_URL = "mysql://user:password@localhost:3306/dbname"
|
||||
# ^^^^^^^^^ 指向 backend 自己
|
||||
```
|
||||
|
||||
### ✅ 正确写法
|
||||
```python
|
||||
# 使用 docker-compose 中的服务名
|
||||
DATABASE_URL = "mysql://user:password@database:3306/dbname"
|
||||
# ^^^^^^^^ 数据库服务名
|
||||
```
|
||||
|
||||
## 实际示例
|
||||
|
||||
### 示例 1:Spring Boot + MySQL
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
springboot-app:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: jdbc:mysql://mysql-db:3306/mydb
|
||||
SPRING_DATASOURCE_USERNAME: user
|
||||
SPRING_DATASOURCE_PASSWORD: password
|
||||
depends_on:
|
||||
- mysql-db
|
||||
|
||||
mysql-db:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_DATABASE: mydb
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
ports:
|
||||
- "3306:3306"
|
||||
```
|
||||
|
||||
### 示例 2:Node.js + PostgreSQL
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
api:
|
||||
build: ./api
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
DATABASE_HOST: postgres
|
||||
DATABASE_PORT: 5432
|
||||
DATABASE_NAME: myapp
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
postgres:
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_DB: myapp
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: password
|
||||
ports:
|
||||
- "5432:5432"
|
||||
```
|
||||
|
||||
```javascript
|
||||
// .env
|
||||
DATABASE_URL=postgres://user:password@postgres:5432/myapp
|
||||
// ^^^^^^^^ 服务名
|
||||
```
|
||||
|
||||
### 示例 3:Python/Django + MongoDB
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
MONGO_HOST: mongodb
|
||||
MONGO_PORT: 27017
|
||||
depends_on:
|
||||
- mongodb
|
||||
|
||||
mongodb:
|
||||
image: mongo:6.0
|
||||
ports:
|
||||
- "27017:27017"
|
||||
```
|
||||
|
||||
```python
|
||||
# settings.py
|
||||
MONGO_URI = "mongodb://mongodb:27017/mydb"
|
||||
# ^^^^^^^^ 使用服务名
|
||||
```
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 进入容器测试连接
|
||||
```bash
|
||||
# 进入后端容器
|
||||
docker exec -it <backend-container> bash
|
||||
|
||||
# 测试数据库连接
|
||||
ping database
|
||||
telnet database 3306
|
||||
nc -zv database 3306
|
||||
```
|
||||
|
||||
### 2. 查看网络信息
|
||||
```bash
|
||||
# 查看容器网络
|
||||
docker network inspect <network-name>
|
||||
|
||||
# 查看容器 IP 和别名
|
||||
docker inspect <database-container> --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
|
||||
```
|
||||
|
||||
### 3. 使用 docker network 连接
|
||||
```bash
|
||||
# 从宿主机通过容器 IP 连接(不推荐)
|
||||
docker inspect <database-container> | grep IPAddress
|
||||
|
||||
# 正确方式:从另一个容器连接
|
||||
docker run --rm --network <network-name> mysql:8.0 \
|
||||
mysql -h database -u user -ppassword dbname
|
||||
```
|
||||
|
||||
## 特殊情况
|
||||
|
||||
### 从宿主机连接数据库
|
||||
|
||||
如果需要从**宿主机**(非 Docker 容器)连接数据库,使用:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
- "3306:3306" # 映射到宿主机
|
||||
```
|
||||
|
||||
**连接方式:**
|
||||
```python
|
||||
# 从宿主机运行的应用
|
||||
DATABASE_URL = "mysql://user:password@localhost:3306/dbname"
|
||||
```
|
||||
|
||||
**注意:** 这是唯一可以使用 `localhost` 的情况——从宿主机连接暴露端口的容器。
|
||||
|
||||
### 开发环境 vs 生产环境
|
||||
|
||||
```python
|
||||
# 开发环境:可能直接使用本地数据库
|
||||
if os.getenv("ENVIRONMENT") == "development":
|
||||
DATABASE_URL = "mysql://user:password@localhost:3306/devdb"
|
||||
else:
|
||||
# 生产环境:使用 Docker 网络中的数据库
|
||||
DATABASE_URL = "mysql://user:password@database:3306/proddb"
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用环境变量**:将数据库连接配置为环境变量,便于在不同环境间切换
|
||||
2. **使用服务名**:优先使用 docker-compose 中的服务名,保持配置一致性
|
||||
3. **健康检查**:使用 `depends_on` + 健康检查确保数据库就绪后再启动应用
|
||||
4. **网络隔离**:为不同环境使用不同的网络
|
||||
5. **安全性**:不要在代码中硬编码密码,使用 secrets 或环境变量
|
||||
|
||||
## 故障排查清单
|
||||
|
||||
- [ ] 检查 docker-compose.yml 中数据库服务的名称
|
||||
- [ ] 确认后端使用的是服务名而非 `localhost`
|
||||
- [ ] 确认数据库服务在同一个网桥网络中
|
||||
- [ ] 检查端口映射配置(如果从宿主机连接)
|
||||
- [ ] 验证容器间的网络连通性(使用 `ping` 或 `telnet`)
|
||||
- [ ] 检查防火墙规则
|
||||
- [ ] 查看容器日志确认数据库是否正常启动
|
||||
|
||||
## 参考资源
|
||||
|
||||
- [Docker Networking](https://docs.docker.com/network/)
|
||||
- [Docker Compose Service Discovery](https://docs.docker.com/compose/networking/)
|
||||
- [Connect to MySQL from another container](https://hub.docker.com/_/mysql)
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2026-04-21
|
||||
@@ -0,0 +1,545 @@
|
||||
---
|
||||
tags: [go, configuration, environment, viper, godotenv, assignment]
|
||||
create time: 2026-04-21
|
||||
---
|
||||
|
||||
# Viper 和 Godotenv 对比分析
|
||||
|
||||
## 概述
|
||||
|
||||
Go 语言中有两个常用的配置管理库:**Viper** 和 **Godotenv**。虽然它们都与配置管理相关,但设计目标和使用场景存在显著差异。
|
||||
|
||||
- **Viper**: 完整的配置解决方案,支持多种配置源
|
||||
- **Godotenv**: 专注于从 `.env` 文件加载环境变量
|
||||
|
||||
## 核心对比
|
||||
|
||||
| 特性 | Viper | Godotenv |
|
||||
|-----|-------|----------|
|
||||
| **主要用途** | 配置管理框架 | 环境变量加载 |
|
||||
| **配置源数量** | 多种(JSON/YAML/ENV/Flags) | 仅 `.env` 文件 |
|
||||
| **功能范围** | 完整配置系统 | 单一功能库 |
|
||||
| **代码复杂度** | 较大 | 简洁 |
|
||||
| **学习曲线** | 较复杂 | 简单 |
|
||||
| **适用场景** | 复杂应用配置 | 简单环境变量管理 |
|
||||
|
||||
## 设计理念对比
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Viper - 配置管理框架"
|
||||
A1[JSON] --> A[统一配置 API]
|
||||
B1[YAML] --> A
|
||||
C1[TOML] --> A
|
||||
D1[环境变量] --> A
|
||||
E1[命令行参数] --> A
|
||||
A --> F[配置读取/更新/监听]
|
||||
end
|
||||
|
||||
subgraph "Godotenv - 环境变量加载"
|
||||
G[.env 文件] --> H[解析]
|
||||
H --> I[设置环境变量]
|
||||
end
|
||||
|
||||
style A fill:#e1f5ff
|
||||
style I fill:#fff4e1
|
||||
```
|
||||
|
||||
## Viper 详解
|
||||
|
||||
### 功能特性
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((Viper))
|
||||
配置源
|
||||
JSON
|
||||
YAML
|
||||
TOML
|
||||
环境变量
|
||||
命令行参数
|
||||
功能
|
||||
自动重载
|
||||
远程配置中心
|
||||
配置合并
|
||||
结构体绑定
|
||||
优势
|
||||
多源配置
|
||||
优先级管理
|
||||
类型安全
|
||||
```
|
||||
|
||||
### 基础使用
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func viperExample() {
|
||||
// 1. 初始化
|
||||
v := viper.New()
|
||||
|
||||
// 2. 设置配置文件 (自动查找 app.json/app.yaml 等)
|
||||
v.SetConfigName("config")
|
||||
v.SetConfigType("yaml")
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("./config")
|
||||
|
||||
// 3. 读取配置文件
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
panic(fmt.Errorf("读取配置失败: %w", err))
|
||||
}
|
||||
|
||||
// 4. 读取配置值
|
||||
dbHost := v.GetString("database.host")
|
||||
dbPort := v.GetInt("database.port")
|
||||
debug := v.GetBool("debug")
|
||||
|
||||
fmt.Printf("数据库: %s:%d, Debug: %v\n", dbHost, dbPort, debug)
|
||||
}
|
||||
```
|
||||
|
||||
### 结构体绑定
|
||||
|
||||
```go
|
||||
func structBindingExample() {
|
||||
type Config struct {
|
||||
AppName string `mapstructure:"app_name"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
|
||||
Database struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
} `mapstructure:"database"`
|
||||
}
|
||||
|
||||
var config Config
|
||||
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("应用: %s, 数据库: %s:%d\n",
|
||||
config.AppName, config.Database.Host, config.Database.Port)
|
||||
}
|
||||
```
|
||||
|
||||
### 多配置源优先级
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[默认值] --> B[配置文件]
|
||||
B --> C[环境变量]
|
||||
C --> D[命令行参数]
|
||||
D --> E[最终配置]
|
||||
|
||||
style A fill:#f9f9f9
|
||||
style E fill:#66bb6a,color:#fff
|
||||
```
|
||||
|
||||
```go
|
||||
func multiSourceExample() {
|
||||
v := viper.New()
|
||||
|
||||
// 1. 设置默认值 (最低优先级)
|
||||
v.SetDefault("server.port", 8080)
|
||||
v.SetDefault("log.level", "info")
|
||||
|
||||
// 2. 读取配置文件
|
||||
v.SetConfigFile("config.yaml")
|
||||
v.ReadInConfig()
|
||||
|
||||
// 3. 绑定环境变量 (可覆盖配置文件)
|
||||
v.SetEnvPrefix("APP") // APP_SERVER_PORT
|
||||
v.BindEnv("server.port")
|
||||
|
||||
// 4. 绑定命令行参数 (最高优先级, 使用 pflag)
|
||||
pflag.Int("port", 0, "服务器端口")
|
||||
pflag.Parse()
|
||||
v.BindPFlags(pflag.CommandLine)
|
||||
|
||||
// 最终取值: 命令行参数 > 环境变量 > 配置文件 > 默认值
|
||||
}
|
||||
```
|
||||
|
||||
### 自动重载配置
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
func watchConfig() {
|
||||
v := viper.New()
|
||||
v.SetConfigFile("config.yaml")
|
||||
v.ReadInConfig()
|
||||
|
||||
// 监听配置文件变化
|
||||
v.WatchConfig()
|
||||
v.OnConfigChange(func(e fsnotify.Event) {
|
||||
fmt.Printf("配置文件已更改: %s\n", e.Name)
|
||||
// 自动重载, 无需手动调用 ReadInConfig
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 配置文件示例 (config.yaml)
|
||||
|
||||
```yaml
|
||||
app_name: "my-app"
|
||||
debug: true
|
||||
|
||||
database:
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
user: "admin"
|
||||
password: "secret"
|
||||
|
||||
redis:
|
||||
addr: "localhost:6379"
|
||||
pool_size: 10
|
||||
|
||||
log:
|
||||
level: "debug"
|
||||
format: "json"
|
||||
```
|
||||
|
||||
## Godotenv 详解
|
||||
|
||||
### 功能特性
|
||||
|
||||
- ✅ 从 `.env` 文件加载环境变量
|
||||
- ✅ 支持注释 (# 开头)
|
||||
- ✅ 支持引号和换行
|
||||
- ❌ 不支持配置优先级
|
||||
- ❌ 不支持远程配置
|
||||
- ❌ 不支持自动重载
|
||||
|
||||
### 基础使用
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func godotenvExample() {
|
||||
// 1. 加载 .env 文件到环境变量
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Fatal("加载 .env 文件失败", err)
|
||||
}
|
||||
|
||||
// 2. 直接读取环境变量
|
||||
dbHost := os.Getenv("DB_HOST")
|
||||
dbPort := os.Getenv("DB_PORT")
|
||||
apiKey := os.Getenv("API_KEY")
|
||||
|
||||
fmt.Printf("数据库: %s:%s, API Key: %s\n", dbHost, dbPort, apiKey)
|
||||
}
|
||||
```
|
||||
|
||||
### 指定文件路径
|
||||
|
||||
```go
|
||||
// 加载指定路径的 .env 文件
|
||||
godotenv.Load(".env.production")
|
||||
godotenv.Load("/path/to/.env")
|
||||
|
||||
// 加载多个 .env 文件 (后加载的会覆盖先加载的)
|
||||
godotenv.Load(".env", ".env.local", ".env.secrets")
|
||||
```
|
||||
|
||||
### .env 文件示例
|
||||
|
||||
```bash
|
||||
# 数据库配置
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=admin
|
||||
DB_PASSWORD=secret123
|
||||
|
||||
# API 配置
|
||||
API_BASE_URL=https://api.example.com
|
||||
API_KEY=sk-1234567890abcdef
|
||||
|
||||
# 应用配置
|
||||
APP_NAME=my-app
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
```
|
||||
|
||||
### 配合 Viper 使用
|
||||
|
||||
```go
|
||||
func combinedExample() {
|
||||
// 1. 先用 godotenv 加载 .env
|
||||
godotenv.Load()
|
||||
|
||||
// 2. 再用 viper 读取环境变量
|
||||
viper.AutomaticEnv() // 自动读取环境变量
|
||||
|
||||
// 3. 可以设置默认值
|
||||
viper.SetDefault("DB_PORT", 5432)
|
||||
|
||||
// 4. 读取配置
|
||||
dbHost := viper.GetString("DB_HOST")
|
||||
dbPort := viper.GetInt("DB_PORT")
|
||||
}
|
||||
```
|
||||
|
||||
## 使用场景分析
|
||||
|
||||
### 适用 Viper 的场景
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[复杂应用配置] --> B[多配置源]
|
||||
A --> C[需要优先级管理]
|
||||
A --> D[结构化配置文件]
|
||||
A --> E[配置热更新]
|
||||
|
||||
B --> B1[JSON/YAML/TOML]
|
||||
C --> C1[默认值 < 文件 < ENV < CLI]
|
||||
D --> D1[嵌套结构]
|
||||
E --> E1[监听文件变化]
|
||||
```
|
||||
|
||||
**典型应用:**
|
||||
- 微服务配置管理
|
||||
- 需要多种配置来源的应用
|
||||
- 需要配置优先级和覆盖机制
|
||||
- 需要远程配置中心集成的应用
|
||||
|
||||
### 适用 Godotenv 的场景
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[简单环境变量管理] --> B[12-Factor App]
|
||||
A --> C[Docker 容器化]
|
||||
A --> D[开发环境隔离]
|
||||
|
||||
B --> B1[配置即代码]
|
||||
C --> C1[容器环境变量]
|
||||
D --> D1[.env.dev / .env.prod]
|
||||
```
|
||||
|
||||
**典型应用:**
|
||||
- 12-Factor 应用
|
||||
- Docker/Kubernetes 部署
|
||||
- 简单脚本或小型应用
|
||||
- 仅需要环境变量的项目
|
||||
|
||||
## 代码对比示例
|
||||
|
||||
### 场景: 从配置文件读取数据库配置
|
||||
|
||||
#### 使用 Viper
|
||||
|
||||
```go
|
||||
// config.yaml
|
||||
/*
|
||||
database:
|
||||
host: localhost
|
||||
port: 5432
|
||||
user: admin
|
||||
password: secret123
|
||||
*/
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
// 代码
|
||||
viper.SetConfigFile("config.yaml")
|
||||
viper.ReadInConfig()
|
||||
|
||||
var config DatabaseConfig
|
||||
viper.UnmarshalKey("database", &config)
|
||||
|
||||
db, err := sql.Open("postgres",
|
||||
fmt.Sprintf("%s:%s@%s:%d/mydb",
|
||||
config.User, config.Password, config.Host, config.Port))
|
||||
```
|
||||
|
||||
#### 使用 Godotenv
|
||||
|
||||
```go
|
||||
// .env
|
||||
/*
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=admin
|
||||
DB_PASSWORD=secret123
|
||||
*/
|
||||
|
||||
// 代码
|
||||
godotenv.Load()
|
||||
|
||||
db, err := sql.Open("postgres",
|
||||
fmt.Sprintf("%s:%s@%s:%s/mydb",
|
||||
os.Getenv("DB_USER"),
|
||||
os.Getenv("DB_PASSWORD"),
|
||||
os.Getenv("DB_HOST"),
|
||||
os.Getenv("DB_PORT")))
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### Viper 最佳实践
|
||||
|
||||
```go
|
||||
// 1. 封装配置加载
|
||||
func LoadConfig() (*Config, error) {
|
||||
v := viper.New()
|
||||
|
||||
// 设置配置文件默认路径
|
||||
v.SetConfigFile("config/config.yaml")
|
||||
|
||||
// 允许环境变量覆盖
|
||||
v.SetEnvPrefix("APP")
|
||||
v.AutomaticEnv()
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// 2. 配置验证
|
||||
func (c *Config) Validate() error {
|
||||
if c.Database.Host == "" {
|
||||
return errors.New("数据库主机不能为空")
|
||||
}
|
||||
if c.Database.Port <= 0 || c.Database.Port > 65535 {
|
||||
return errors.New("端口号无效")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Godotenv 最佳实践
|
||||
|
||||
```bash
|
||||
# .env.example (提交到版本控制)
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=admin
|
||||
DB_PASSWORD=
|
||||
|
||||
# .env (不提交,加入 .gitignore)
|
||||
DB_HOST=production-db.example.com
|
||||
DB_PORT=5432
|
||||
DB_USER=admin
|
||||
DB_PASSWORD=real_secret_password
|
||||
```
|
||||
|
||||
```go
|
||||
// 1. 提供默认值
|
||||
func getEnv(key, defaultValue string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// 2. 使用
|
||||
dbHost := getEnv("DB_HOST", "localhost")
|
||||
dbPort := getEnv("DB_PORT", "5432")
|
||||
```
|
||||
|
||||
### 混合使用策略
|
||||
|
||||
```go
|
||||
// config.yaml (结构化配置)
|
||||
app:
|
||||
name: "my-app"
|
||||
version: "1.0.0"
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
// .env (敏感信息)
|
||||
DB_PASSWORD=secret123
|
||||
API_SECRET=xyz789
|
||||
|
||||
// 代码
|
||||
func main() {
|
||||
// 1. 加载环境变量 (敏感配置)
|
||||
godotenv.Load()
|
||||
|
||||
// 2. 加载配置文件 (结构化配置)
|
||||
viper.SetConfigFile("config.yaml")
|
||||
viper.ReadInConfig()
|
||||
|
||||
// 3. 合并配置
|
||||
viper.BindEnv("database.password", "DB_PASSWORD")
|
||||
viper.BindEnv("api.secret", "API_SECRET")
|
||||
|
||||
// 4. 读取最终配置
|
||||
config := loadConfig()
|
||||
|
||||
// 在代码中,结构化配置在 YAML,敏感配置在 .env
|
||||
}
|
||||
```
|
||||
|
||||
## 项目选择建议
|
||||
|
||||
### 选择 Viper,如果你需要:
|
||||
|
||||
- ✅ 支持多种配置格式 (JSON/YAML/TOML)
|
||||
- ✅ 配置优先级管理
|
||||
- ✅ 结构体绑定和类型安全
|
||||
- ✅ 配置热更新
|
||||
- ✅ 远程配置中心集成
|
||||
- ✅ 命令行参数绑定
|
||||
|
||||
### 选择 Godotenv,如果你需要:
|
||||
|
||||
- ✅ 12-Factor 应用规范
|
||||
- ✅ Docker/Kubernetes 部署
|
||||
- ✅ 极简配置管理
|
||||
- ✅ 环境变量即可满足需求
|
||||
- ✅ 团队熟悉 `.env` 工作流
|
||||
- ✅ 依赖最小化
|
||||
|
||||
## 学习资源
|
||||
|
||||
### Viper
|
||||
|
||||
- [Viper 官方文档](https://github.com/spf13/viper)
|
||||
- [Viper 使用指南](https://www.sohamkamani.com/golang/viper-guide/)
|
||||
- [Go 配置管理最佳实践](https://sagikazarmark.hu/blog/2022/03/02/manage-go-configuration/)
|
||||
|
||||
### Godotenv
|
||||
|
||||
- [Godotenv 官方文档](https://github.com/joho/godotenv)
|
||||
- [12-Factor App - Config](https://12factor.net/config)
|
||||
- [.env 文件规范](https://dotenv.org/)
|
||||
|
||||
## 相关笔记
|
||||
|
||||
- [[CS/LANG/Go/标准库/标准库]] - Go 语言标准库
|
||||
- [[CS/DevOps/Docker/基础]] - Docker 容器化
|
||||
- [[CS/NET/Auth/密钥管理]] - 配置安全最佳实践
|
||||
- [[金山办公作业/Week05/数据库设计]] - 数据库配置详解
|
||||
+23
-23
@@ -20,7 +20,7 @@ erDiagram
|
||||
string password "密码(hash)"
|
||||
datetime created_at "创建时间"
|
||||
datetime updated_at "更新时间"
|
||||
datetime deleted_at "删除时间(软删除)"
|
||||
tinyint is_deleted "软删除标记"
|
||||
}
|
||||
WORD {
|
||||
uuid id PK "单词记录ID"
|
||||
@@ -31,7 +31,7 @@ erDiagram
|
||||
string ai_provider "AI模型来源"
|
||||
datetime created_at "创建时间"
|
||||
datetime updated_at "更新时间"
|
||||
datetime deleted_at "删除时间(软删除)"
|
||||
tinyint is_deleted "软删除标记"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ erDiagram
|
||||
| `password` | VARCHAR(255) | NOT NULL | - | 用户密码,存储 bcrypt 哈希值,严禁明文存储 |
|
||||
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | - | 账户创建时间 |
|
||||
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | - | 账户最后更新时间 |
|
||||
| `deleted_at` | DATETIME | DEFAULT NULL | INDEX | 软删除标记,NULL 表示未删除 |
|
||||
| `is_deleted` | TINYINT | DEFAULT 0 | INDEX | 软删除标记,0 表示未删除,1 表示已删除 |
|
||||
|
||||
**索引设计:**
|
||||
- 主键索引:`id`
|
||||
@@ -60,23 +60,23 @@ erDiagram
|
||||
| 单词表名 | `words` |
|
||||
|-----------|---------|
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 索引 | 说明 |
|
||||
| ------------- | ------------ | --------------------------- | ------------------------- | ------------------------------------------ |
|
||||
| `id` | CHAR(36) | PRIMARY KEY | - | 单词记录唯一标识符(UUID 格式) |
|
||||
| `user_id` | CHAR(36) | NOT NULL, FOREIGN KEY | - | 所属用户 ID,关联 users.id |
|
||||
| `word` | VARCHAR(100) | NOT NULL | INDEX idx_word_search(20) | 单词文本,支持前缀搜索优化 |
|
||||
| `definition` | TEXT | NOT NULL | - | AI 生成的单词释义 |
|
||||
| `examples` | JSON | NOT NULL | - | 例句列表,存储 JSON 数组格式 |
|
||||
| `ai_provider` | ENUM | NOT NULL | - | AI 模型来源标识(`deepseek`=DeepSeek,`qwen`=通义千问) |
|
||||
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | - | 单词记录创建时间 |
|
||||
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | - | 单词记录最后更新时间 |
|
||||
| `deleted_at` | DATETIME | DEFAULT NULL | INDEX idx_user_word | 软删除标记 |
|
||||
| 字段名 | 数据类型 | 约束 | 索引 | 说明 |
|
||||
|--------|----------|------|------|------|
|
||||
| `id` | CHAR(36) | PRIMARY KEY | - | 单词记录唯一标识符(UUID 格式) |
|
||||
| `user_id` | CHAR(36) | NOT NULL, FOREIGN KEY | - | 所属用户 ID,关联 users.id |
|
||||
| `word` | VARCHAR(100) | NOT NULL | INDEX idx_word_search(20) | 单词文本,支持前缀搜索优化 |
|
||||
| `definition` | TEXT | NOT NULL | - | AI 生成的单词释义 |
|
||||
| `examples` | JSON | NOT NULL | - | 例句列表,存储 JSON 数组格式 |
|
||||
| `ai_provider` | ENUM | NOT NULL | - | AI 模型来源标识(`deepseek`=DeepSeek,`qwen`=通义千问) |
|
||||
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | - | 单词记录创建时间 |
|
||||
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | - | 单词记录最后更新时间 |
|
||||
| `is_deleted` | TINYINT | DEFAULT 0 | INDEX idx_user_word | 软删除标记 |
|
||||
|
||||
**索引设计:**
|
||||
- 主键索引:`id`
|
||||
- 外键索引:`user_id`(自动创建)
|
||||
- 唯一索引:`uk_user_word` (user_id, word(50)) - 防止同一用户保存重复单词
|
||||
- 复合索引:`idx_user_word` (user_id, deleted_at) - 优化用户单词列表分页查询
|
||||
- 复合索引:`idx_user_word` (user_id, is_deleted) - 优化用户单词列表分页查询
|
||||
- 前缀索引:`idx_word_search` (word(20)) - 支持单词前缀搜索优化
|
||||
|
||||
## 四、表关联关系
|
||||
@@ -97,15 +97,15 @@ erDiagram
|
||||
```sql
|
||||
-- 查询某用户的所有单词(排除已删除)
|
||||
SELECT * FROM words
|
||||
WHERE user_id = ? AND deleted_at IS NULL
|
||||
WHERE user_id = ? AND is_deleted = 0
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?;
|
||||
|
||||
-- 联表查询用户信息及单词数量
|
||||
SELECT u.id, u.username, COUNT(w.id) as word_count
|
||||
FROM users u
|
||||
LEFT JOIN words w ON u.id = w.user_id AND w.deleted_at IS NULL
|
||||
WHERE u.deleted_at IS NULL
|
||||
LEFT JOIN words w ON u.id = w.user_id AND w.is_deleted = 0
|
||||
WHERE u.is_deleted = 0
|
||||
GROUP BY u.id;
|
||||
```
|
||||
|
||||
@@ -126,7 +126,7 @@ GROUP BY u.id;
|
||||
-- 查询:检查用户是否已保存该单词
|
||||
SELECT id, word, definition, examples, ai_provider
|
||||
FROM words
|
||||
WHERE user_id = ? AND word = ? AND deleted_at IS NULL;
|
||||
WHERE user_id = ? AND word = ? AND is_deleted = 0;
|
||||
```
|
||||
|
||||
### 5.2 手动保存单词
|
||||
@@ -147,14 +147,14 @@ VALUES (?, ?, ?, ?, ?, ?);
|
||||
-- 查询:分页获取单词列表(按创建时间倒序)
|
||||
SELECT id, word, definition, examples, ai_provider, created_at
|
||||
FROM words
|
||||
WHERE user_id = ? AND deleted_at IS NULL
|
||||
WHERE user_id = ? AND is_deleted = 0
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?;
|
||||
|
||||
-- 计算总数(用于分页器)
|
||||
SELECT COUNT(*) as total
|
||||
FROM words
|
||||
WHERE user_id = ? AND deleted_at IS NULL;
|
||||
WHERE user_id = ? AND is_deleted = 0;
|
||||
```
|
||||
|
||||
### 5.4 删除单词(软删除)
|
||||
@@ -164,8 +164,8 @@ WHERE user_id = ? AND deleted_at IS NULL;
|
||||
```sql
|
||||
-- 更新:软删除单词记录
|
||||
UPDATE words
|
||||
SET deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ? AND deleted_at IS NULL;
|
||||
SET is_deleted = 1, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ? AND is_deleted = 0;
|
||||
```
|
||||
|
||||
## 六、索引优化说明
|
||||
|
||||
Reference in New Issue
Block a user